966 lines
38 KiB
Rust
966 lines
38 KiB
Rust
//! This module contains the semantic analysis of the AST.
|
|
|
|
#![allow(clippy::missing_errors_doc)]
|
|
|
|
use crate::{
|
|
base::{self, source_file::SourceElement as _, Handler},
|
|
lexical::token::{KeywordKind, MacroStringLiteral, MacroStringLiteralPart},
|
|
syntax::syntax_tree::{
|
|
declaration::{Declaration, Function, FunctionVariableType, ImportItems},
|
|
expression::{Binary, BinaryOperator, Expression, LuaCode, PrefixOperator, Primary},
|
|
program::{Namespace, ProgramFile},
|
|
statement::{
|
|
execute_block::{
|
|
Conditional, Else, ExecuteBlock, ExecuteBlockHead, ExecuteBlockHeadItem as _,
|
|
ExecuteBlockTail,
|
|
},
|
|
Assignment, AssignmentDestination, Block, Grouping, Semicolon, SemicolonStatement,
|
|
Statement, VariableDeclaration,
|
|
},
|
|
AnyStringLiteral,
|
|
},
|
|
transpile::{
|
|
error::{
|
|
AssignmentError, IllegalIndexing, IllegalIndexingReason, MismatchedTypes,
|
|
UnknownIdentifier,
|
|
},
|
|
expression::{ExpectedType, ValueType},
|
|
},
|
|
};
|
|
|
|
pub mod error;
|
|
|
|
mod scope;
|
|
use error::{IncompatibleFunctionAnnotation, InvalidNamespaceName, UnexpectedExpression};
|
|
pub use scope::{SemanticScope, VariableType};
|
|
|
|
use super::syntax::syntax_tree::ConnectedList;
|
|
|
|
impl ProgramFile {
|
|
/// Analyzes the semantics of the program.
|
|
pub fn analyze_semantics(
|
|
&self,
|
|
handler: &impl Handler<base::Error>,
|
|
) -> Result<(), error::Error> {
|
|
self.namespace().analyze_semantics(handler)?;
|
|
|
|
let mut errs = Vec::new();
|
|
let scope = get_global_scope(self.declarations());
|
|
|
|
for declaration in self.declarations() {
|
|
if let Err(err) = declaration.analyze_semantics(&scope, handler) {
|
|
errs.push(err);
|
|
}
|
|
}
|
|
|
|
errs.into_iter().next().map_or(Ok(()), Err)
|
|
}
|
|
}
|
|
|
|
fn get_global_scope(declarations: &[Declaration]) -> SemanticScope<'static> {
|
|
let scope = SemanticScope::new();
|
|
|
|
for declaration in declarations {
|
|
declaration.add_to_semantic_scope(&scope);
|
|
}
|
|
|
|
scope
|
|
}
|
|
|
|
impl Namespace {
|
|
/// Analyzes the semantics of the namespace.
|
|
fn analyze_semantics(&self, handler: &impl Handler<base::Error>) -> Result<(), error::Error> {
|
|
let name = self.name();
|
|
Self::validate_str(name.str_content().as_ref()).map_err(|invalid_chars| {
|
|
let err = error::Error::from(InvalidNamespaceName {
|
|
name: name.clone(),
|
|
invalid_chars,
|
|
});
|
|
handler.receive(err.clone());
|
|
err
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Declaration {
|
|
fn add_to_semantic_scope(&self, scope: &SemanticScope) {
|
|
match self {
|
|
Self::Function(func) => {
|
|
let name = func.identifier();
|
|
scope.set_variable(name.span.str(), VariableType::Function);
|
|
}
|
|
Self::GlobalVariable((_, var, _)) => {
|
|
let name = var.identifier();
|
|
let var_type = match var {
|
|
VariableDeclaration::Array(arr) => match arr.variable_type().keyword {
|
|
KeywordKind::Bool => VariableType::BooleanStorageArray,
|
|
KeywordKind::Int => VariableType::ScoreboardArray,
|
|
_ => unreachable!("variable type is not a valid type"),
|
|
},
|
|
VariableDeclaration::Score(_) => VariableType::Scoreboard,
|
|
VariableDeclaration::Tag(_) => VariableType::Tag,
|
|
VariableDeclaration::Single(single) => match single.variable_type().keyword {
|
|
KeywordKind::Bool => VariableType::BooleanStorage,
|
|
KeywordKind::Int => VariableType::ScoreboardValue,
|
|
_ => unreachable!("variable type is not a valid type"),
|
|
},
|
|
VariableDeclaration::ComptimeValue(_) => VariableType::ComptimeValue,
|
|
};
|
|
scope.set_variable(name.span.str(), var_type);
|
|
}
|
|
Self::Import(_) | Self::Tag(_) => {}
|
|
}
|
|
}
|
|
|
|
/// Analyzes the semantics of the declaration.
|
|
fn analyze_semantics(
|
|
&self,
|
|
scope: &SemanticScope,
|
|
handler: &impl Handler<base::Error>,
|
|
) -> Result<(), error::Error> {
|
|
match self {
|
|
Self::Function(func) => func.analyze_semantics(scope, handler),
|
|
|
|
Self::Import(imp) => match imp.items() {
|
|
ImportItems::All(_) => {
|
|
let err = error::Error::Other(
|
|
"Importing all items is not yet supported.".to_string(),
|
|
);
|
|
handler.receive(err.clone());
|
|
Err(err)
|
|
}
|
|
ImportItems::Named(items) => {
|
|
for item in items.elements() {
|
|
if scope.get_variable(item.span.str()) == Some(VariableType::Function) {
|
|
let err = error::Error::from(error::ConflictingFunctionNames {
|
|
name: item.span.str().to_string(),
|
|
definition: item.span(),
|
|
});
|
|
handler.receive(err.clone());
|
|
return Err(err);
|
|
}
|
|
scope.set_variable(item.span.str(), VariableType::Function);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
},
|
|
|
|
Self::GlobalVariable((_, var, _)) => var.analyze_semantics(scope, handler),
|
|
|
|
Self::Tag(_) => Ok(()),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Function {
|
|
fn analyze_semantics(
|
|
&self,
|
|
scope: &SemanticScope,
|
|
handler: &impl Handler<base::Error>,
|
|
) -> Result<(), error::Error> {
|
|
let child_scope = SemanticScope::with_parent(scope);
|
|
|
|
if let Some(parameters) = self.parameters().as_ref().map(ConnectedList::elements) {
|
|
if let Some(incompatible) = self.annotations().iter().find(|a| {
|
|
["tick", "load", "uninstall"].contains(&a.assignment().identifier.span.str())
|
|
}) {
|
|
let err =
|
|
error::Error::IncompatibleFunctionAnnotation(IncompatibleFunctionAnnotation {
|
|
span: incompatible.assignment().identifier.span.clone(),
|
|
reason:
|
|
"functions with the `tick`, `load` or `uninstall` annotation cannot have parameters"
|
|
.to_string(),
|
|
});
|
|
handler.receive(err.clone());
|
|
return Err(err);
|
|
}
|
|
|
|
for param in parameters {
|
|
let name = param.identifier().span.str();
|
|
let var = match param.variable_type() {
|
|
FunctionVariableType::Boolean(_) => VariableType::BooleanStorage,
|
|
FunctionVariableType::Integer(_) => VariableType::ScoreboardValue,
|
|
FunctionVariableType::Macro(_) => VariableType::MacroParameter,
|
|
};
|
|
child_scope.set_variable(name, var);
|
|
}
|
|
}
|
|
|
|
self.block().analyze_semantics(&child_scope, handler)?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl Block {
|
|
fn analyze_semantics(
|
|
&self,
|
|
scope: &SemanticScope,
|
|
handler: &impl Handler<base::Error>,
|
|
) -> Result<(), error::Error> {
|
|
let mut errs = Vec::new();
|
|
|
|
for statement in self.statements() {
|
|
if let Err(err) = statement.analyze_semantics(scope, handler) {
|
|
errs.push(err);
|
|
}
|
|
}
|
|
|
|
errs.first().map_or(Ok(()), |err| Err(err.clone()))
|
|
}
|
|
}
|
|
|
|
impl Statement {
|
|
fn analyze_semantics(
|
|
&self,
|
|
scope: &SemanticScope,
|
|
handler: &impl Handler<base::Error>,
|
|
) -> Result<(), error::Error> {
|
|
match self {
|
|
Self::Block(block) => {
|
|
let child_scope = SemanticScope::with_parent(scope);
|
|
block.analyze_semantics(&child_scope, handler)
|
|
}
|
|
Self::DocComment(_) | Self::LiteralCommand(_) => Ok(()),
|
|
Self::ExecuteBlock(ex) => ex.analyze_semantics(scope, handler),
|
|
Self::Grouping(group) => {
|
|
let child_scope = SemanticScope::with_parent(scope);
|
|
group.analyze_semantics(&child_scope, handler)
|
|
}
|
|
Self::Semicolon(sem) => sem.analyze_semantics(scope, handler),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ExecuteBlock {
|
|
fn analyze_semantics(
|
|
&self,
|
|
scope: &SemanticScope,
|
|
handler: &impl Handler<base::Error>,
|
|
) -> Result<(), error::Error> {
|
|
match self {
|
|
Self::HeadTail(head, tail) => {
|
|
let head_res = head.analyze_semantics(scope, handler);
|
|
let tail_res = tail.analyze_semantics(scope, handler);
|
|
|
|
if head_res.is_err() {
|
|
head_res
|
|
} else {
|
|
tail_res
|
|
}
|
|
}
|
|
Self::IfElse(cond, then, el) => {
|
|
let cond_res = cond.analyze_semantics(scope, handler);
|
|
let then_scope = SemanticScope::with_parent(scope);
|
|
let then_res = then.analyze_semantics(&then_scope, handler);
|
|
let el_scope = SemanticScope::with_parent(scope);
|
|
let else_res = el.analyze_semantics(&el_scope, handler);
|
|
|
|
if cond_res.is_err() {
|
|
cond_res
|
|
} else if then_res.is_err() {
|
|
then_res
|
|
} else {
|
|
else_res
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ExecuteBlockHead {
|
|
fn analyze_semantics(
|
|
&self,
|
|
scope: &SemanticScope,
|
|
handler: &impl Handler<base::Error>,
|
|
) -> Result<(), error::Error> {
|
|
match self {
|
|
Self::Align(align) => align.analyze_semantics(scope, handler),
|
|
Self::Anchored(anchored) => anchored.analyze_semantics(scope, handler),
|
|
Self::As(r#as) => r#as.analyze_semantics(scope, handler),
|
|
Self::At(at) => at.analyze_semantics(scope, handler),
|
|
Self::AsAt(asat) => asat.analyze_semantics(scope, handler),
|
|
Self::Conditional(cond) => cond.analyze_semantics(scope, handler),
|
|
Self::Facing(facing) => facing.analyze_semantics(scope, handler),
|
|
Self::In(r#in) => r#in.analyze_semantics(scope, handler),
|
|
Self::On(on) => on.analyze_semantics(scope, handler),
|
|
Self::Positioned(pos) => pos.analyze_semantics(scope, handler),
|
|
Self::Rotated(rot) => rot.analyze_semantics(scope, handler),
|
|
Self::Store(store) => store.analyze_semantics(scope, handler),
|
|
Self::Summon(summon) => summon.analyze_semantics(scope, handler),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ExecuteBlockTail {
|
|
fn analyze_semantics(
|
|
&self,
|
|
scope: &SemanticScope,
|
|
handler: &impl Handler<base::Error>,
|
|
) -> Result<(), error::Error> {
|
|
match self {
|
|
Self::Block(block) => {
|
|
let child_scope = SemanticScope::with_parent(scope);
|
|
block.analyze_semantics(&child_scope, handler)
|
|
}
|
|
Self::ExecuteBlock(_, ex) => ex.analyze_semantics(scope, handler),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Else {
|
|
fn analyze_semantics(
|
|
&self,
|
|
scope: &SemanticScope,
|
|
handler: &impl Handler<base::Error>,
|
|
) -> Result<(), error::Error> {
|
|
let child_scope = SemanticScope::with_parent(scope);
|
|
self.block().analyze_semantics(&child_scope, handler)
|
|
}
|
|
}
|
|
|
|
impl Conditional {
|
|
/// Analyzes the semantics of the conditional.
|
|
pub fn analyze_semantics(
|
|
&self,
|
|
scope: &SemanticScope,
|
|
handler: &impl Handler<base::Error>,
|
|
) -> Result<(), error::Error> {
|
|
self.condition().analyze_semantics(scope, handler)
|
|
}
|
|
}
|
|
|
|
impl Grouping {
|
|
fn analyze_semantics(
|
|
&self,
|
|
scope: &SemanticScope,
|
|
handler: &impl Handler<base::Error>,
|
|
) -> Result<(), error::Error> {
|
|
self.block().analyze_semantics(scope, handler)
|
|
}
|
|
}
|
|
|
|
impl Semicolon {
|
|
fn analyze_semantics(
|
|
&self,
|
|
scope: &SemanticScope,
|
|
handler: &impl Handler<base::Error>,
|
|
) -> Result<(), error::Error> {
|
|
match self.statement() {
|
|
SemicolonStatement::Assignment(assignment) => {
|
|
assignment.analyze_semantics(scope, handler)
|
|
}
|
|
SemicolonStatement::Expression(expr) => expr.analyze_semantics(scope, handler),
|
|
SemicolonStatement::VariableDeclaration(decl) => decl.analyze_semantics(scope, handler),
|
|
SemicolonStatement::Return(ret) => ret.expression().analyze_semantics(scope, handler),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Assignment {
|
|
fn analyze_semantics(
|
|
&self,
|
|
scope: &SemanticScope,
|
|
handler: &impl Handler<base::Error>,
|
|
) -> Result<(), error::Error> {
|
|
let variable_type = match self.destination() {
|
|
AssignmentDestination::Identifier(ident) => scope.get_variable(ident.span.str()),
|
|
AssignmentDestination::Indexed(ident, _, index, _) => {
|
|
let var = scope.get_variable(ident.span.str());
|
|
index.analyze_semantics(scope, handler)?;
|
|
let valid_index = match var {
|
|
Some(VariableType::Tag | VariableType::Scoreboard) => (!index
|
|
.can_yield_type_semantics(ValueType::String, scope))
|
|
.then_some(ExpectedType::String),
|
|
Some(VariableType::ScoreboardArray | VariableType::BooleanStorageArray) => {
|
|
(!index.can_yield_type_semantics(ValueType::Integer, scope))
|
|
.then_some(ExpectedType::Integer)
|
|
}
|
|
_ => None,
|
|
};
|
|
if let Some(expected) = valid_index {
|
|
let err = error::Error::IllegalIndexing(IllegalIndexing {
|
|
expression: index.span(),
|
|
reason: IllegalIndexingReason::InvalidComptimeType { expected },
|
|
});
|
|
handler.receive(err.clone());
|
|
return Err(err);
|
|
}
|
|
var
|
|
}
|
|
};
|
|
|
|
if let Some(variable_type) = variable_type {
|
|
let expected = match variable_type {
|
|
VariableType::BooleanStorage => ValueType::Boolean,
|
|
VariableType::ScoreboardValue => ValueType::Integer,
|
|
VariableType::ComptimeValue => {
|
|
// TODO: check if the expression is a constant expression
|
|
return Ok(());
|
|
}
|
|
VariableType::BooleanStorageArray | VariableType::Tag
|
|
if matches!(self.destination(), AssignmentDestination::Indexed(..)) =>
|
|
{
|
|
ValueType::Boolean
|
|
}
|
|
VariableType::Scoreboard | VariableType::ScoreboardArray
|
|
if matches!(self.destination(), AssignmentDestination::Indexed(..)) =>
|
|
{
|
|
ValueType::Integer
|
|
}
|
|
_ => {
|
|
let err = error::Error::AssignmentError(AssignmentError {
|
|
identifier: self.destination().span(),
|
|
message: "cannot assign to this type".to_string(),
|
|
});
|
|
handler.receive(err.clone());
|
|
return Err(err);
|
|
}
|
|
};
|
|
|
|
if !self.expression().can_yield_type_semantics(expected, scope) {
|
|
let err = error::Error::MismatchedTypes(MismatchedTypes {
|
|
expected_type: expected.into(),
|
|
expression: self.expression().span(),
|
|
});
|
|
handler.receive(err.clone());
|
|
return Err(err);
|
|
}
|
|
} else {
|
|
let err = error::Error::UnknownIdentifier(UnknownIdentifier {
|
|
identifier: self.destination().span(),
|
|
});
|
|
handler.receive(err.clone());
|
|
return Err(err);
|
|
}
|
|
self.expression().analyze_semantics(scope, handler)
|
|
}
|
|
}
|
|
|
|
impl VariableDeclaration {
|
|
fn analyze_semantics(
|
|
&self,
|
|
scope: &SemanticScope,
|
|
handler: &impl Handler<base::Error>,
|
|
) -> Result<(), error::Error> {
|
|
let name = self.identifier().span.str();
|
|
let (var, expected) = match self {
|
|
Self::Array(_) => match self.variable_type().keyword {
|
|
KeywordKind::Bool => (VariableType::BooleanStorageArray, ExpectedType::Boolean),
|
|
KeywordKind::Int => (VariableType::ScoreboardArray, ExpectedType::Integer),
|
|
_ => unreachable!("variable type is not a valid type"),
|
|
},
|
|
Self::Score(_) => (VariableType::Scoreboard, ExpectedType::Integer),
|
|
Self::Tag(_) => (VariableType::Tag, ExpectedType::Boolean),
|
|
Self::Single(_) => match self.variable_type().keyword {
|
|
KeywordKind::Bool => (VariableType::BooleanStorage, ExpectedType::Boolean),
|
|
KeywordKind::Int => (VariableType::ScoreboardValue, ExpectedType::Integer),
|
|
_ => unreachable!("variable type is not a valid type"),
|
|
},
|
|
Self::ComptimeValue(decl) => {
|
|
if let Some(assignment) = decl.assignment() {
|
|
assignment.expression().analyze_semantics(scope, handler)?;
|
|
}
|
|
(VariableType::ComptimeValue, ExpectedType::Any)
|
|
}
|
|
};
|
|
scope.set_variable(name, var);
|
|
let assignment = match self {
|
|
Self::Array(arr) => arr.assignment().as_ref(),
|
|
Self::Score(scr) => scr.assignment().as_ref(),
|
|
Self::Tag(tag) => {
|
|
if let Some((target, assignment)) = tag.target_assignment() {
|
|
target.analyze_semantics(scope, handler)?;
|
|
Some(assignment)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
Self::Single(single) => single.assignment().as_ref(),
|
|
Self::ComptimeValue(decl) => decl.assignment().as_ref(),
|
|
};
|
|
if var == VariableType::ComptimeValue {
|
|
// TODO: check if the expression is a constant expression
|
|
return Ok(());
|
|
}
|
|
if let Some(assignment) = assignment {
|
|
let expected = match var {
|
|
VariableType::BooleanStorage | VariableType::Tag => ValueType::Boolean,
|
|
VariableType::ScoreboardValue => ValueType::Integer,
|
|
_ => {
|
|
let err = error::Error::MismatchedTypes(MismatchedTypes {
|
|
expected_type: expected,
|
|
expression: assignment.expression().span(),
|
|
});
|
|
handler.receive(err.clone());
|
|
return Err(err);
|
|
}
|
|
};
|
|
if !assignment
|
|
.expression()
|
|
.can_yield_type_semantics(expected, scope)
|
|
{
|
|
let err = error::Error::MismatchedTypes(MismatchedTypes {
|
|
expected_type: expected.into(),
|
|
expression: assignment.expression().span(),
|
|
});
|
|
handler.receive(err.clone());
|
|
return Err(err);
|
|
}
|
|
assignment.expression().analyze_semantics(scope, handler)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl Expression {
|
|
fn analyze_semantics(
|
|
&self,
|
|
scope: &SemanticScope,
|
|
handler: &impl Handler<base::Error>,
|
|
) -> Result<(), error::Error> {
|
|
match self {
|
|
Self::Primary(primary) => primary.analyze_semantics(scope, handler),
|
|
Self::Binary(binary) => binary.analyze_semantics(scope, handler),
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
fn can_yield_type_semantics(&self, expected: ValueType, scope: &SemanticScope) -> bool {
|
|
match self {
|
|
Self::Primary(primary) => primary.can_yield_type_semantics(expected, scope),
|
|
Self::Binary(binary) => binary.can_yield_type_semantics(expected, scope),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Primary {
|
|
#[expect(clippy::too_many_lines)]
|
|
fn analyze_semantics(
|
|
&self,
|
|
scope: &SemanticScope,
|
|
handler: &impl Handler<base::Error>,
|
|
) -> Result<(), error::Error> {
|
|
match self {
|
|
Self::Identifier(ident) => {
|
|
if scope.get_variable(ident.span.str()).is_none() {
|
|
let err = error::Error::UnknownIdentifier(UnknownIdentifier {
|
|
identifier: ident.span.clone(),
|
|
});
|
|
handler.receive(err.clone());
|
|
Err(err)
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
Self::Boolean(_) | Self::Integer(_) | Self::StringLiteral(_) => Ok(()),
|
|
Self::MacroStringLiteral(lit) => lit.analyze_semantics(scope, handler),
|
|
Self::FunctionCall(call) => {
|
|
let var = scope.get_variable(call.identifier().span.str());
|
|
var.map_or_else(
|
|
|| {
|
|
let err = error::Error::UnknownIdentifier(UnknownIdentifier {
|
|
identifier: call.identifier().span.clone(),
|
|
});
|
|
handler.receive(err.clone());
|
|
Err(err)
|
|
},
|
|
|var| match var {
|
|
VariableType::Function | VariableType::InternalFunction => Ok(()),
|
|
_ => {
|
|
let err = error::Error::UnexpectedExpression(UnexpectedExpression(
|
|
Box::new(Expression::Primary(self.clone())),
|
|
));
|
|
handler.receive(err.clone());
|
|
Err(err)
|
|
}
|
|
},
|
|
)
|
|
}
|
|
Self::Indexed(indexed) => {
|
|
if let Self::Identifier(ident) = indexed.object().as_ref() {
|
|
let variable_type = scope.get_variable(ident.span.str());
|
|
match variable_type {
|
|
Some(VariableType::BooleanStorageArray | VariableType::ScoreboardArray) => {
|
|
if !indexed
|
|
.index()
|
|
.can_yield_type_semantics(ValueType::Integer, scope)
|
|
{
|
|
let err = error::Error::IllegalIndexing(IllegalIndexing {
|
|
reason: IllegalIndexingReason::InvalidComptimeType {
|
|
expected: ExpectedType::Integer,
|
|
},
|
|
expression: indexed.index().span(),
|
|
});
|
|
handler.receive(err.clone());
|
|
return Err(err);
|
|
}
|
|
}
|
|
Some(VariableType::Tag | VariableType::Scoreboard) => {
|
|
if !indexed
|
|
.index()
|
|
.can_yield_type_semantics(ValueType::String, scope)
|
|
{
|
|
let err = error::Error::IllegalIndexing(IllegalIndexing {
|
|
reason: IllegalIndexingReason::InvalidComptimeType {
|
|
expected: ExpectedType::String,
|
|
},
|
|
expression: indexed.index().span(),
|
|
});
|
|
handler.receive(err.clone());
|
|
return Err(err);
|
|
}
|
|
}
|
|
_ => {
|
|
let err = error::Error::IllegalIndexing(IllegalIndexing {
|
|
reason: IllegalIndexingReason::NotIndexable,
|
|
expression: indexed.object().span(),
|
|
});
|
|
handler.receive(err.clone());
|
|
return Err(err);
|
|
}
|
|
}
|
|
indexed.object().analyze_semantics(scope, handler)?;
|
|
indexed.index().analyze_semantics(scope, handler)
|
|
} else {
|
|
let err = error::Error::IllegalIndexing(IllegalIndexing {
|
|
reason: IllegalIndexingReason::NotIdentifier,
|
|
expression: indexed.object().span(),
|
|
});
|
|
handler.receive(err.clone());
|
|
Err(err)
|
|
}
|
|
}
|
|
Self::MemberAccess(_) => {
|
|
// TODO:
|
|
Ok(())
|
|
}
|
|
Self::Parenthesized(expr) => expr.analyze_semantics(scope, handler),
|
|
Self::Prefix(prefixed) => match prefixed.operator() {
|
|
PrefixOperator::LogicalNot(_) => {
|
|
if prefixed
|
|
.operand()
|
|
.can_yield_type_semantics(ValueType::Boolean, scope)
|
|
{
|
|
prefixed.operand().analyze_semantics(scope, handler)
|
|
} else {
|
|
let err = error::Error::MismatchedTypes(MismatchedTypes {
|
|
expected_type: ExpectedType::Boolean,
|
|
expression: prefixed.operand().span(),
|
|
});
|
|
handler.receive(err.clone());
|
|
Err(err)
|
|
}
|
|
}
|
|
PrefixOperator::Negate(_) => {
|
|
if prefixed
|
|
.operand()
|
|
.can_yield_type_semantics(ValueType::Integer, scope)
|
|
{
|
|
prefixed.operand().analyze_semantics(scope, handler)
|
|
} else {
|
|
let err = error::Error::MismatchedTypes(MismatchedTypes {
|
|
expected_type: ExpectedType::Integer,
|
|
expression: prefixed.operand().span(),
|
|
});
|
|
handler.receive(err.clone());
|
|
Err(err)
|
|
}
|
|
}
|
|
PrefixOperator::Run(_) => {
|
|
if prefixed
|
|
.operand()
|
|
.can_yield_type_semantics(ValueType::String, scope)
|
|
{
|
|
prefixed.operand().analyze_semantics(scope, handler)
|
|
} else {
|
|
let err = error::Error::MismatchedTypes(MismatchedTypes {
|
|
expected_type: ExpectedType::String,
|
|
expression: prefixed.operand().span(),
|
|
});
|
|
handler.receive(err.clone());
|
|
Err(err)
|
|
}
|
|
}
|
|
},
|
|
Self::Lua(lua) => lua.analyze_semantics(scope, handler),
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
fn can_yield_type_semantics(&self, expected: ValueType, scope: &SemanticScope) -> bool {
|
|
match self {
|
|
Self::Boolean(_) => expected == ValueType::Boolean,
|
|
Self::Integer(_) => expected == ValueType::Integer,
|
|
Self::StringLiteral(_) | Self::MacroStringLiteral(_) => {
|
|
matches!(expected, ValueType::String | ValueType::Boolean)
|
|
}
|
|
Self::FunctionCall(_) => matches!(expected, ValueType::Boolean | ValueType::Integer),
|
|
Self::Indexed(indexed) => match indexed.object().as_ref() {
|
|
Self::Identifier(ident) => {
|
|
let var = scope.get_variable(ident.span.str());
|
|
match (var, expected) {
|
|
(Some(VariableType::BooleanStorageArray), ValueType::Boolean)
|
|
| (Some(VariableType::ScoreboardArray), ValueType::Integer) => indexed
|
|
.index()
|
|
.can_yield_type_semantics(ValueType::Integer, scope),
|
|
(Some(VariableType::Tag), ValueType::Boolean)
|
|
| (Some(VariableType::Scoreboard), ValueType::Integer) => indexed
|
|
.index()
|
|
.can_yield_type_semantics(ValueType::String, scope),
|
|
_ => false,
|
|
}
|
|
}
|
|
_ => false,
|
|
},
|
|
Self::MemberAccess(_) => {
|
|
// TODO: correct return value after check
|
|
true
|
|
}
|
|
Self::Identifier(ident) => match scope.get_variable(ident.span.str()) {
|
|
Some(VariableType::BooleanStorage) => expected == ValueType::Boolean,
|
|
Some(VariableType::ScoreboardValue) => expected == ValueType::Integer,
|
|
Some(VariableType::Tag) => expected == ValueType::String,
|
|
Some(VariableType::ComptimeValue) => true,
|
|
_ => false,
|
|
},
|
|
Self::Prefix(prefixed) => match prefixed.operator() {
|
|
PrefixOperator::LogicalNot(_) => {
|
|
expected == ValueType::Boolean
|
|
&& prefixed
|
|
.operand()
|
|
.can_yield_type_semantics(ValueType::Boolean, scope)
|
|
}
|
|
PrefixOperator::Negate(_) => {
|
|
expected == ValueType::Integer
|
|
&& prefixed
|
|
.operand()
|
|
.can_yield_type_semantics(ValueType::Integer, scope)
|
|
}
|
|
PrefixOperator::Run(_) => {
|
|
expected == ValueType::String
|
|
&& prefixed
|
|
.operand()
|
|
.can_yield_type_semantics(ValueType::String, scope)
|
|
}
|
|
},
|
|
Self::Parenthesized(parenthesized) => {
|
|
parenthesized.can_yield_type_semantics(expected, scope)
|
|
}
|
|
Self::Lua(lua) => lua.can_yield_type_semantics(expected),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Binary {
|
|
fn analyze_semantics(
|
|
&self,
|
|
scope: &SemanticScope,
|
|
handler: &impl Handler<base::Error>,
|
|
) -> Result<(), error::Error> {
|
|
match self.operator() {
|
|
BinaryOperator::Add(_) => {
|
|
if (self
|
|
.left_operand()
|
|
.can_yield_type_semantics(ValueType::Integer, scope)
|
|
&& self
|
|
.right_operand()
|
|
.can_yield_type_semantics(ValueType::Integer, scope))
|
|
|| self
|
|
.left_operand()
|
|
.can_yield_type_semantics(ValueType::String, scope)
|
|
|| self
|
|
.right_operand()
|
|
.can_yield_type_semantics(ValueType::String, scope)
|
|
{
|
|
self.left_operand().analyze_semantics(scope, handler)?;
|
|
self.right_operand().analyze_semantics(scope, handler)
|
|
} else {
|
|
let err = error::Error::MismatchedTypes(MismatchedTypes {
|
|
expected_type: ExpectedType::AnyOf(vec![
|
|
ExpectedType::Integer,
|
|
ExpectedType::String,
|
|
]),
|
|
expression: self.span(),
|
|
});
|
|
handler.receive(err.clone());
|
|
Err(err)
|
|
}
|
|
}
|
|
BinaryOperator::Subtract(_)
|
|
| BinaryOperator::Multiply(_)
|
|
| BinaryOperator::Divide(_)
|
|
| BinaryOperator::Modulo(_) => {
|
|
if self
|
|
.left_operand()
|
|
.can_yield_type_semantics(ValueType::Integer, scope)
|
|
&& self
|
|
.right_operand()
|
|
.can_yield_type_semantics(ValueType::Integer, scope)
|
|
{
|
|
self.left_operand().analyze_semantics(scope, handler)?;
|
|
self.right_operand().analyze_semantics(scope, handler)
|
|
} else {
|
|
let err = error::Error::MismatchedTypes(MismatchedTypes {
|
|
expected_type: ExpectedType::Integer,
|
|
expression: self.span(),
|
|
});
|
|
handler.receive(err.clone());
|
|
Err(err)
|
|
}
|
|
}
|
|
BinaryOperator::Equal(..)
|
|
| BinaryOperator::NotEqual(..)
|
|
| BinaryOperator::GreaterThan(_)
|
|
| BinaryOperator::GreaterThanOrEqual(..)
|
|
| BinaryOperator::LessThan(_)
|
|
| BinaryOperator::LessThanOrEqual(..) => {
|
|
self.left_operand().analyze_semantics(scope, handler)?;
|
|
self.right_operand().analyze_semantics(scope, handler)
|
|
}
|
|
BinaryOperator::LogicalAnd(..) | BinaryOperator::LogicalOr(..) => {
|
|
if self
|
|
.left_operand()
|
|
.can_yield_type_semantics(ValueType::Boolean, scope)
|
|
&& self
|
|
.right_operand()
|
|
.can_yield_type_semantics(ValueType::Boolean, scope)
|
|
{
|
|
self.left_operand().analyze_semantics(scope, handler)?;
|
|
self.right_operand().analyze_semantics(scope, handler)
|
|
} else {
|
|
let err = error::Error::MismatchedTypes(MismatchedTypes {
|
|
expected_type: ExpectedType::Boolean,
|
|
expression: self.span(),
|
|
});
|
|
handler.receive(err.clone());
|
|
Err(err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
fn can_yield_type_semantics(&self, expected: ValueType, scope: &SemanticScope) -> bool {
|
|
match self.operator() {
|
|
BinaryOperator::Add(_) => {
|
|
if expected == ValueType::Integer {
|
|
self.left_operand()
|
|
.can_yield_type_semantics(ValueType::Integer, scope)
|
|
&& self
|
|
.right_operand()
|
|
.can_yield_type_semantics(ValueType::Integer, scope)
|
|
} else {
|
|
expected == ValueType::String
|
|
}
|
|
}
|
|
BinaryOperator::Subtract(_)
|
|
| BinaryOperator::Multiply(_)
|
|
| BinaryOperator::Divide(_)
|
|
| BinaryOperator::Modulo(_) => {
|
|
expected == ValueType::Integer
|
|
&& self
|
|
.left_operand()
|
|
.can_yield_type_semantics(ValueType::Integer, scope)
|
|
&& self
|
|
.right_operand()
|
|
.can_yield_type_semantics(ValueType::Integer, scope)
|
|
}
|
|
BinaryOperator::Equal(..)
|
|
| BinaryOperator::NotEqual(..)
|
|
| BinaryOperator::GreaterThan(_)
|
|
| BinaryOperator::GreaterThanOrEqual(..)
|
|
| BinaryOperator::LessThan(_)
|
|
| BinaryOperator::LessThanOrEqual(..) => expected == ValueType::Boolean,
|
|
BinaryOperator::LogicalAnd(..) | BinaryOperator::LogicalOr(..) => {
|
|
expected == ValueType::Boolean
|
|
&& self
|
|
.left_operand()
|
|
.can_yield_type_semantics(ValueType::Boolean, scope)
|
|
&& self
|
|
.right_operand()
|
|
.can_yield_type_semantics(ValueType::Boolean, scope)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl LuaCode {
|
|
#[expect(clippy::unused_self)]
|
|
#[cfg_attr(feature = "lua", expect(unused_variables, clippy::unnecessary_wraps))]
|
|
fn analyze_semantics(
|
|
&self,
|
|
_scope: &SemanticScope,
|
|
handler: &impl Handler<base::Error>,
|
|
) -> Result<(), error::Error> {
|
|
cfg_if::cfg_if! {
|
|
if #[cfg(feature = "lua")] {
|
|
// TODO: check which type is returned
|
|
Ok(())
|
|
} else {
|
|
let err = error::Error::LuaDisabled;
|
|
handler.receive(err.clone());
|
|
Err(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
#[expect(clippy::unused_self)]
|
|
#[must_use]
|
|
fn can_yield_type_semantics(&self, _expected: ValueType) -> bool {
|
|
// TODO: check which type is returned
|
|
cfg!(feature = "lua")
|
|
}
|
|
}
|
|
|
|
impl AnyStringLiteral {
|
|
pub(crate) fn analyze_semantics(
|
|
&self,
|
|
scope: &SemanticScope,
|
|
handler: &impl Handler<base::Error>,
|
|
) -> Result<(), error::Error> {
|
|
match self {
|
|
Self::StringLiteral(_) => Ok(()),
|
|
Self::MacroStringLiteral(lit) => lit.analyze_semantics(scope, handler),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl MacroStringLiteral {
|
|
fn analyze_semantics(
|
|
&self,
|
|
scope: &SemanticScope,
|
|
handler: &impl Handler<base::Error>,
|
|
) -> Result<(), error::Error> {
|
|
let mut errs = Vec::new();
|
|
|
|
for part in self.parts() {
|
|
match part {
|
|
MacroStringLiteralPart::MacroUsage { identifier, .. } => {
|
|
if let Some(variable_type) = scope.get_variable(identifier.span.str()) {
|
|
if variable_type != VariableType::MacroParameter {
|
|
let err =
|
|
error::Error::UnexpectedExpression(UnexpectedExpression(Box::new(
|
|
Expression::Primary(Primary::Identifier(identifier.clone())),
|
|
)));
|
|
handler.receive(err.clone());
|
|
errs.push(err);
|
|
}
|
|
} else {
|
|
let err = error::Error::UnknownIdentifier(UnknownIdentifier {
|
|
identifier: identifier.span.clone(),
|
|
});
|
|
handler.receive(err.clone());
|
|
errs.push(err);
|
|
}
|
|
}
|
|
MacroStringLiteralPart::Text(_) => {}
|
|
}
|
|
}
|
|
|
|
errs.into_iter().next().map_or(Ok(()), Err)
|
|
}
|
|
}
|