use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::sync::Arc;
use crate::types::GameFnPtr;
use crate::ntstring::NTStr;
use crate::ast::{
GrugType, UnaryOperator, BinaryOperator,
ExprData, HelperFunction, Statement, Expr,
Argument,
};
use crate::frontend::GlobalStatement;
use crate::nt;
use crate::arena::Arena;
use crate::frontend::parser::AST;
use crate::mod_api::{ModApiEntity, ModApiGameFn};
use allocator_api2::vec::Vec;
use allocator_api2::boxed::Box;
pub(super) struct TypePropogator<'mod_api, 'arena> {
entity: &'mod_api ModApiEntity<'mod_api>,
game_fns: &'mod_api HashMap<&'mod_api NTStr, ModApiGameFn<'mod_api>>,
game_fn_ptrs: &'arena HashMap<&'static str, GameFnPtr>,
current_mod_name: String,
global_variables: HashMap<&'arena str, GrugType<'arena>>,
local_variables: Vec<HashMap<&'arena str, GrugType<'arena>>>,
num_while_loops_deep: usize,
current_fn_name: Option<&'arena str>,
}
#[derive(Debug, Clone)]
pub enum OwnedGrugType {
Void,
Bool,
Number,
String,
Id{custom_name: Option<Box<str>>},
Resource{extension: Box<str>},
Entity{entity_type: Option<Box<str>>},
}
impl From<&mut GrugType<'_>> for OwnedGrugType {
fn from(other: &mut GrugType<'_>) -> Self {
(*other).into()
}
}
impl From<&GrugType<'_>> for OwnedGrugType {
fn from(other: &GrugType<'_>) -> Self {
(*other).into()
}
}
impl From<GrugType<'_>> for OwnedGrugType {
fn from(other: GrugType<'_>) -> Self {
match other {
GrugType::Void => OwnedGrugType::Void,
GrugType::Bool => OwnedGrugType::Bool,
GrugType::Number => OwnedGrugType::Number,
GrugType::String => OwnedGrugType::String,
GrugType::Id{custom_name} => OwnedGrugType::Id{custom_name: custom_name.map(|name| Box::from(name.to_str()))},
GrugType::Resource{extension} => OwnedGrugType::Resource{extension: Box::from(extension.to_str())},
GrugType::Entity{entity_type} => OwnedGrugType::Entity{entity_type: entity_type.map(|entity_type| Box::from(entity_type.to_str()))},
}
}
}
impl std::fmt::Display for OwnedGrugType {
fn fmt (&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
match self {
Self::Void => write!(f, "void"),
Self::Bool => write!(f, "bool"),
Self::Number => write!(f, "number"),
Self::String => write!(f, "string"),
Self::Id{
custom_name: None,
} => write!(f, "id"),
Self::Id{
custom_name: Some(custom_name),
} => write!(f, "{}", custom_name),
Self::Resource {
extension: _,
} => write!(f, "resource"),
Self::Entity {
entity_type: Some(name),
} => write!(f, "{}", name),
Self::Entity {
entity_type: None,
} => write!(f, "entity"),
}
}
}
#[derive(Debug)]
pub enum TypePropogatorError {
EntityDoesNotExist{
entity_name: Arc<str>,
},
GlobalVariableShadowed {
name: Arc<str>,
},
GlobalCantCallHelperFn {
global_name: Arc<str>,
},
VariableDoesNotExist {
name: Arc<str>,
},
AdjacentUnaryOperators {
operator: UnaryOperator,
},
NotOperatorNotBeforeBool{
got: OwnedGrugType,
},
MinusOperatorNotBeforeNumber {
got: OwnedGrugType,
},
CannotCompareStrings {
operator: BinaryOperator,
},
BinaryOperatorTypeMismatch {
operator: BinaryOperator,
left: OwnedGrugType,
right: OwnedGrugType,
},
LogicalOperatorExpectsBool {
operator: BinaryOperator,
},
ComparisonOperatorExpectsNumber {
operator: BinaryOperator,
got_type: OwnedGrugType,
},
ArithmeticOperatorExpectsNumber {
operator: BinaryOperator,
got_type: OwnedGrugType,
},
RemainderOperatorExpectsNumber {
got_ty: OwnedGrugType,
},
CallOnFnWithinOnFn {
on_fn_name: Arc<str>,
},
FunctionDoesNotExist {
function_name: Arc<str>,
},
TooFewArguments{
function_name: Arc<str>,
expected_name: Arc<str>,
expected_type: OwnedGrugType,
},
TooManyArguments{
function_name: Arc<str>,
got_type: OwnedGrugType,
},
ResourceValidationError(ResourceValidationError),
EntityValidationError(EntityValidationError),
VoidArgumentInFunctionCall {
function_name: Arc<str>,
signature_type: OwnedGrugType,
parameter_name: Arc<str>
},
FunctionArgumentMismatch {
function_name: Arc<str>,
expected_type: OwnedGrugType,
got_type: OwnedGrugType,
parameter_name: Arc<str>
},
OnFnDoesNotExist {
function_name: Arc<str>,
entity_name: Arc<str>,
},
TooFewParameters{
function_name: Arc<str>,
expected_name: Arc<str>,
expected_type: OwnedGrugType,
},
TooManyParameters{
function_name: Arc<str>,
parameter_name: Arc<str>,
parameter_type: OwnedGrugType,
},
OnFnParameterNameMismatch{
function_name: Arc<str>,
got_name: Arc<str>,
expected_name: Arc<str>,
},
OnFnParameterTypeMismatch{
function_name: Arc<str>,
parameter_name: Arc<str>,
got_type: OwnedGrugType,
expected_type: OwnedGrugType,
},
GlobalCantBeAssignedMe {
name: Arc<str>,
},
VariableTypeMismatch {
name: Arc<str>,
got_type: OwnedGrugType,
expected_type: OwnedGrugType,
},
LocalVariableShadowedByGlobal {
name: Arc<str>,
},
LocalVariableShadowedByLocal {
name: Arc<str>,
},
CantAssignBecauseVariableDoesntExist {
name: Arc<str>,
},
IfConditionTypeMismatch {
got_type: OwnedGrugType,
},
WhileConditionTypeMismatch {
got_type: OwnedGrugType,
},
BreakStatementOutsideWhileLoop,
ContinueStatementOutsideWhileLoop,
GlobalIdsCantBeReassigned,
VariableAlreadyExists {
variable_name: Arc<str>
},
MismatchedReturnType {
function_name: Arc<str>,
expected_type: OwnedGrugType,
got_type: OwnedGrugType,
},
LastStatementNotReturn {
function_name: Arc<str>,
expected_return_type: OwnedGrugType,
},
OutOfOrderOnFn {
entity_name: Arc<str>,
on_fn_name: Arc<str>,
},
GameFunctionNotProvided {
fn_name: String,
}
}
impl std::fmt::Display for TypePropogatorError {
fn fmt (&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
match self {
Self::EntityDoesNotExist{
entity_name,
} => write!(f, "The entity '{}' was not declared by mod_api.json", entity_name),
Self::GlobalVariableShadowed {
name,
} => write!(f, "The global variable '{}' shadows an earlier global variable with the same name, so change the name of one of them", name),
Self::GlobalCantCallHelperFn {
global_name,
} => write!(f, "The global variable '{}' isn't allowed to call helper functions", global_name),
Self::VariableDoesNotExist {
name,
} => write!(f, "The variable '{}' does not exist", name),
Self::AdjacentUnaryOperators {
operator,
} => write!(f, "Found '{0}' directly next to another '{0}', which can be simplified by just removing both of them", operator),
Self::NotOperatorNotBeforeBool{
got,
} => write!(f, "Found 'not' before {}, but it can only be put before a bool", got),
Self::MinusOperatorNotBeforeNumber {
got,
} => write!(f, "Found '-' before {}, but it can only be put before a number", got),
Self::CannotCompareStrings {
operator,
} => write!(f, "You can't use the {} operator on a string", operator),
Self::BinaryOperatorTypeMismatch {
operator,
left: OwnedGrugType::String,
right: _,
} => write!(f, "You can't use the {} operator on a string", operator),
Self::BinaryOperatorTypeMismatch {
operator,
left: _,
right: OwnedGrugType::String,
} => write!(f, "You can't use the {} operator on a string", operator),
Self::BinaryOperatorTypeMismatch {
operator,
left,
right,
} => write!(f, "The left and right operand of a binary expression ('{}') must have the same type, but got {} and {}", operator, left, right),
Self::LogicalOperatorExpectsBool {
operator,
} => write!(f, "'{}' operator expects bool", operator),
Self::ComparisonOperatorExpectsNumber {
operator,
got_type: _,
} => write!(f, "'{}' operator expects number", operator),
Self::ArithmeticOperatorExpectsNumber {
got_type: OwnedGrugType::String,
operator,
} => write!(f, "You can't use the {} operator on a string", operator),
Self::ArithmeticOperatorExpectsNumber {
operator,
got_type: _,
} => write!(f, "'{}' operator expects number", operator),
Self::RemainderOperatorExpectsNumber {
got_ty: _,
} => write!(f, "'%' operator expects number"),
Self::CallOnFnWithinOnFn {
on_fn_name,
} => write!(f, "Mods aren't allowed to call their own on_ functions, but '{}' was called", on_fn_name),
Self::FunctionDoesNotExist {
function_name,
} if function_name.starts_with("helper_") => write!(f, "The helper function '{}' was not defined by this grug file", function_name),
Self::FunctionDoesNotExist {
function_name,
} => write!(f, "The game function '{}' was not declared by mod_api.json", function_name),
Self::TooFewArguments{
function_name,
expected_name,
expected_type,
} => write!(f, "Function call '{}' expected the argument '{}' with type {}", function_name, expected_name, expected_type),
Self::TooManyArguments{
function_name,
got_type,
} => write!(f, "Function call '{}' got an unexpected extra argument with type {}", function_name, got_type),
Self::ResourceValidationError(error) => write!(f, "{}", error),
Self::EntityValidationError(error) => write!(f, "{}", error),
Self::VoidArgumentInFunctionCall {
function_name,
signature_type,
parameter_name
} => write!(f, "Function call '{}' expected the type {} for argument '{}', but got a function call that doesn't return anything", function_name, signature_type, parameter_name),
Self::FunctionArgumentMismatch {
function_name,
expected_type,
got_type,
parameter_name
} => write!(f, "Function call '{}' expected the type {} for argument '{}', but got {}", function_name, expected_type, parameter_name, got_type),
Self::OnFnDoesNotExist {
function_name,
entity_name,
} => write!(f, "The function '{}' was not declared by entity '{}' in mod_api.json", function_name, entity_name),
Self::TooFewParameters{
function_name,
expected_name,
expected_type,
} => write!(f, "Function '{}' expected the parameter '{}' with type {}", function_name, expected_name, expected_type),
Self::TooManyParameters{
function_name,
parameter_name,
parameter_type,
} => write!(f, "Function '{}' got an unexpected extra parameter '{}' with type {}", function_name, parameter_name, parameter_type),
Self::OnFnParameterNameMismatch{
function_name,
got_name,
expected_name,
} => write!(f, "Function '{}' its '{}' parameter was supposed to be named '{}'", function_name, got_name, expected_name),
Self::OnFnParameterTypeMismatch{
function_name,
parameter_name,
got_type,
expected_type,
} => write!(f, "Function '{}' its '{}' parameter was supposed to have the type {}, but got {}", function_name, parameter_name, expected_type, got_type),
Self::GlobalCantBeAssignedMe {
name: _,
} => write!(f, "Global variables can't be assigned 'me'"),
Self::VariableTypeMismatch {
name,
got_type,
expected_type,
} => write!(f, "Can't assign {} to '{}', which has type {}", got_type, name, expected_type),
Self::LocalVariableShadowedByGlobal {
name,
} => write!(f, "The local variable '{}' shadows an earlier global variable with the same name, so change the name of one of them", name),
Self::LocalVariableShadowedByLocal {
name,
} => write!(f, "The local variable '{}' shadows an earlier local variable with the same name, so change the name of one of them", name),
Self::CantAssignBecauseVariableDoesntExist {
name,
} => write!(f, "Can't assign to the variable '{}', since it does not exist", name),
Self::IfConditionTypeMismatch {
got_type,
} => write!(f, "If condition must be bool but got '{}'", got_type),
Self::WhileConditionTypeMismatch {
got_type,
} => write!(f, "While condition must be bool but got '{}'", got_type),
Self::BreakStatementOutsideWhileLoop => write!(f, "There is a break statement that isn't inside of a while loop"),
Self::ContinueStatementOutsideWhileLoop => write!(f, "There is a continue statement that isn't inside of a while loop"),
Self::GlobalIdsCantBeReassigned => write!(f, "Global id variables can't be reassigned"),
Self::VariableAlreadyExists {
variable_name
} => write!(f, "The variable '{}' already exists", variable_name),
Self::MismatchedReturnType {
function_name,
expected_type,
got_type: OwnedGrugType::Void,
} => write!(f, "Function '{}' is supposed to return a value of type {}", function_name, expected_type),
Self::MismatchedReturnType {
function_name,
expected_type: OwnedGrugType::Void,
got_type: _,
} => write!(f, "Function '{}' wasn't supposed to return any value", function_name),
Self::MismatchedReturnType {
function_name,
expected_type,
got_type,
} => write!(f, "Function '{}' is supposed to return {}, not {}", function_name, expected_type, got_type),
Self::LastStatementNotReturn {
function_name,
expected_return_type,
} => write!(f, "Function '{}' is supposed to return {} as its last line", function_name, expected_return_type),
Self::OutOfOrderOnFn {
entity_name,
on_fn_name,
} => write!(f, "The function '{}' needs to be moved before/after a different on_ function, according to the entity '{}' in mod_api.json", on_fn_name, entity_name),
Self::GameFunctionNotProvided {
fn_name,
} => write!(f, "Game function {} was not registered", fn_name),
}
}
}
#[derive(Debug)]
pub enum ResourceValidationError {
EmptyResource {},
LeadingForwardSlash {
value: Arc<str>
},
TrailingForwardSlash {
value: Arc<str>
},
ContainsBackslash {
value: Arc<str>
},
ContainsDoubleForwardSlash {
value: Arc<str>
},
BeginsWithDotWithoutSlash {
value: Arc<str>
},
ContainsSlashDotInMiddle {
value: Arc<str>
},
BeginsWithDotDotWithoutSlash {
value: Arc<str>
},
ContainsSlashDotDotInMiddle {
value: Arc<str>
},
ExtensionMismatch {
expected: Arc<str>,
value: Arc<str>
},
EndsWithDot {
value: Arc<str>
}
}
impl std::fmt::Display for ResourceValidationError {
fn fmt (&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
match self {
Self::EmptyResource{} => write!(f, "Resources can't be empty strings"),
Self::LeadingForwardSlash {
value
} => write!(f, "Remove the leading slash from the resource \"{}\"", value),
Self::TrailingForwardSlash {
value
} => write!(f, "Remove the trailing slash from the resource \"{}\"", value),
Self::ContainsBackslash {
value
} => write!(f, "Replace the '\\' with '/' in the resource \"{}\"", value),
Self::ContainsDoubleForwardSlash {
value
} => write!(f, "Replace the '//' with '/' in the resource \"{}\"", value),
Self::BeginsWithDotWithoutSlash {
value
} => write!(f, "Remove the '.' from the resource \"{}\"", value),
Self::ContainsSlashDotInMiddle {
value
} => write!(f, "Remove the '.' from the resource \"{}\"", value),
Self::BeginsWithDotDotWithoutSlash {
value
} => write!(f, "Remove the '..' from the resource \"{}\"", value),
Self::ContainsSlashDotDotInMiddle {
value
} => write!(f, "Remove the '..' from the resource \"{}\"", value),
Self::ExtensionMismatch {
expected,
value
} => write!(f, "The resource '{}' was supposed to have the extension '{}'", value, expected),
Self::EndsWithDot {
value
} => write!(f, "resource name \"{}\" cannot end with .", value),
}
}
}
#[derive(Debug)]
pub enum EntityValidationError {
EntityCantBeEmpty,
EntityMissingModName {
entity_string: Arc<str>,
},
EntityMissingEntityName {
mod_name: String,
entity_string: Arc<str>,
},
ModNameIsCurrentMod {
full_entity_string: Arc<str>,
mod_name: String,
entity_name: String,
},
ModNameHasInvalidCharacter {
entity_name: Arc<str>,
invalid_char: char
},
EntityNameHasInvalidCharacter {
entity_name: Arc<str>,
invalid_char: char
},
}
impl std::fmt::Display for EntityValidationError {
fn fmt (&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
match self {
Self::EntityCantBeEmpty => write!(f, "Entities can't be empty strings"),
Self::EntityMissingModName {
entity_string,
} => write!(f, "Entity '{}' is missing a mod name", entity_string),
Self::EntityMissingEntityName {
mod_name,
entity_string,
} => write!(f, "Entity '{}' specifies the mod name '{}', but it is missing an entity name after the ':'", entity_string, mod_name),
Self::ModNameIsCurrentMod {
full_entity_string,
mod_name,
entity_name,
} => write!(f, "Entity '{}' its mod name '{}' is invalid, since the file it is in refers to its own mod; just change it to '{}'", full_entity_string, mod_name, entity_name),
Self::ModNameHasInvalidCharacter {
entity_name,
invalid_char
} => write!(f, "Entity '{}' its mod name contains the invalid character '{}'", entity_name, invalid_char),
Self::EntityNameHasInvalidCharacter {
entity_name,
invalid_char
} => write!(f, "Entity '{}' its entity name contains the invalid character '{}'", entity_name, invalid_char),
}
}
}
impl From<ResourceValidationError> for TypePropogatorError {
fn from (other: ResourceValidationError) -> Self {
Self::ResourceValidationError(other)
}
}
impl From<EntityValidationError> for TypePropogatorError {
fn from (other: EntityValidationError) -> Self {
Self::EntityValidationError(other)
}
}
impl<'mod_api: 'arena, 'arena> TypePropogator<'mod_api, 'arena> {
pub fn new (entity: &'mod_api ModApiEntity, game_fns: &'mod_api HashMap<&'mod_api NTStr, ModApiGameFn>, game_fn_ptrs: &'arena HashMap<&'static str, GameFnPtr>, mod_name: String) -> Self {
Self {
entity,
game_fns,
game_fn_ptrs,
current_mod_name: mod_name,
global_variables: HashMap::new(),
local_variables: Vec::new(),
num_while_loops_deep: 0,
current_fn_name: None,
}
}
pub fn fill_result_types(&mut self, entity_name: &str, ast: &mut AST<'arena>, arena: &'arena Arena) -> Result<(), TypePropogatorError> {
self.add_global_variable(nt!("me"), GrugType::Id{custom_name: Some(Box::leak(NTStr::box_from_str_in(entity_name, arena)).as_ntstrptr())})?;
let variables = ast.global_statements
.iter_mut().filter_map(|st| match st {GlobalStatement::Variable(x) => Some(x), _ => None});
for variable in variables {
self.check_global_expr(&variable.assignment_expr, variable.name.to_str())?;
let result_ty = self.fill_expr(&*ast.helper_fn_signatures, &mut variable.assignment_expr, arena)?;
if let ExprData::Identifier(name) = &variable.assignment_expr.data
&& name.to_str() == "me" {
return Err(TypePropogatorError::GlobalCantBeAssignedMe {
name: Arc::from(name.to_str()),
});
}
if !(variable.ty == GrugType::Id{custom_name: None} && matches!(result_ty, GrugType::Id{..})) && result_ty != variable.ty {
return Err(TypePropogatorError::VariableTypeMismatch {
name: Arc::from(variable.name.to_str()),
got_type: result_ty.into(),
expected_type: variable.ty.into(),
});
}
self.add_global_variable(variable.name.to_str(), result_ty)?;
}
let mut previous_on_fn_index = 0;
let mut on_functions = ast.global_statements
.iter_mut().filter_map(|st| match st {GlobalStatement::OnFunction(x) => Some(x), _ => None})
.collect::<Vec<_>>();
for (on_fn_name, mod_api_on_fn) in
self.entity.on_fns.iter()
{
let Some((current_index, current_on_fn)) =
on_functions.iter_mut().enumerate()
.find(|(_, on_fn)| on_fn.name.to_ntstr() == &**on_fn_name) else
{
continue;
};
if previous_on_fn_index > current_index {
return Err(TypePropogatorError::OutOfOrderOnFn {
entity_name: Arc::from(entity_name),
on_fn_name: Arc::from(current_on_fn.name.to_str()),
});
}
previous_on_fn_index = current_index;
if mod_api_on_fn.arguments.len() > current_on_fn.arguments.len() {
return Err(TypePropogatorError::TooFewParameters{
function_name: Arc::from(current_on_fn.name.to_str()),
expected_name: Arc::from(mod_api_on_fn.arguments[current_on_fn.arguments.len()].name.to_str()),
expected_type: mod_api_on_fn.arguments[current_on_fn.arguments.len()].ty.into(),
});
} else if mod_api_on_fn.arguments.len() < current_on_fn.arguments.len() {
return Err(TypePropogatorError::TooManyParameters{
function_name: Arc::from(current_on_fn.name.to_str()),
parameter_name: Arc::from(current_on_fn.arguments[mod_api_on_fn.arguments.len()].name.to_str()),
parameter_type: current_on_fn.arguments[mod_api_on_fn.arguments.len()].ty.into(),
});
}
for (param, arg) in mod_api_on_fn.arguments.iter().zip(current_on_fn.arguments.iter()) {
if param.name != arg.name {
return Err(TypePropogatorError::OnFnParameterNameMismatch {
function_name: Arc::from(current_on_fn.name.to_str()),
got_name: Arc::from(arg.name.to_str()),
expected_name: Arc::from(param.name.to_str()),
});
}
if param.ty != arg.ty.into() {
return Err(TypePropogatorError::OnFnParameterTypeMismatch {
function_name: Arc::from(current_on_fn.name.to_str()),
parameter_name: Arc::from(param.name.to_str()),
got_type: arg.ty.into(),
expected_type: param.ty.into(),
});
}
}
debug_assert!(self.local_variables.is_empty());
debug_assert!(self.num_while_loops_deep == 0);
debug_assert!(self.current_fn_name.is_none());
self.current_fn_name = Some(current_on_fn.name.to_str());
self.push_scope();
for arg in current_on_fn.arguments {
self.add_local_variable(arg.name.to_str(), arg.ty.into())?;
}
self.fill_statements(&ast.helper_fn_signatures, &mut current_on_fn.body_statements, &GrugType::Void, arena)?;
self.pop_scope();
debug_assert!(self.current_fn_name.as_deref() == Some(current_on_fn.name.to_str()));
self.current_fn_name = None;
}
let entity_on_functions = &self.entity.on_fns;
for on_fn in on_functions {
if !entity_on_functions.iter().any(|(name, _)| **name == *on_fn.name.to_ntstr()) {
return Err(TypePropogatorError::OnFnDoesNotExist {
function_name: Arc::from(on_fn.name.to_str()),
entity_name: Arc::from(entity_name),
});
}
}
for statement in &mut ast.global_statements {
match statement {
GlobalStatement::Variable(_) => (),
GlobalStatement::OnFunction(_) => (),
GlobalStatement::EmptyLine => (),
GlobalStatement::HelperFunction(HelperFunction{
name,
arguments,
body_statements,
return_type,
}) => {
debug_assert!(self.local_variables.is_empty());
debug_assert!(self.num_while_loops_deep == 0);
debug_assert!(self.current_fn_name.is_none());
self.current_fn_name = Some(name.to_str());
self.push_scope();
for arg in *arguments {
self.add_local_variable(arg.name.to_str(), arg.ty.into())?;
}
self.fill_statements(&ast.helper_fn_signatures, body_statements, return_type, arena)?;
if *return_type != GrugType::Void && !matches!(body_statements.last(), Some(Statement::Return{..})) {
return Err(TypePropogatorError::LastStatementNotReturn {
function_name: Arc::from(self.current_fn_name.unwrap()),
expected_return_type: return_type.into(),
});
}
self.pop_scope();
debug_assert!(self.current_fn_name == Some(name.to_str()));
self.current_fn_name = None;
}
GlobalStatement::Comment{..} => (),
}
}
Ok(())
}
fn fill_statements(&mut self, helper_fns: &[(&str, (GrugType<'arena>, &[Argument<'arena>]))], statements: &mut [Statement<'arena>], expected_return_type: &GrugType<'arena>, arena: &'arena Arena) -> Result<(), TypePropogatorError> {
self.push_scope();
for statement in statements {
match statement {
Statement::Variable{
name,
ty,
assignment_expr
} => {
let result_ty = self.fill_expr(helper_fns, assignment_expr, arena)?;
if let Some(ty) = ty {
if self.get_variable_type(name.to_str()).is_some() {
return Err(TypePropogatorError::VariableAlreadyExists{
variable_name: Arc::from(name.to_str()),
});
}
if !(**ty == GrugType::Id{custom_name: None} && matches!(result_ty, GrugType::Id{..})) && **ty != result_ty {
return Err(TypePropogatorError::VariableTypeMismatch {
name: Arc::from(name.to_str()),
got_type: result_ty.into(),
expected_type: (*ty).into(),
});
} else {
self.add_local_variable(name.to_str(), ty.clone())?
}
} else {
let ty = if let Some(ty) = self.get_global_variable_type(name.to_str()) {
if matches!(ty, GrugType::Id {..}) {
return Err(TypePropogatorError::GlobalIdsCantBeReassigned);
}
ty
} else if let Some(ty) = self.get_local_variable_type(name.to_str()) {
ty
} else {
return Err(TypePropogatorError::CantAssignBecauseVariableDoesntExist {
name: Arc::from(name.to_str()),
});
};
if !(ty == GrugType::Id{custom_name: None} && matches!(result_ty, GrugType::Id{..})) && ty != result_ty {
return Err(TypePropogatorError::VariableTypeMismatch {
name: Arc::from(name.to_str()),
got_type: result_ty.into(),
expected_type: ty.into(),
});
}
}
}
Statement::Call(expr) => {
self.fill_expr(helper_fns, expr, arena)?;
}
Statement::If {
condition,
is_chained,
if_block,
else_block,
} => {
let mut condition = condition;
let mut is_chained = is_chained;
let mut if_block = if_block;
let mut else_block = else_block;
loop {
let cond_type = self.fill_expr(helper_fns, condition, arena)?;
if cond_type != GrugType::Bool {
return Err(TypePropogatorError::IfConditionTypeMismatch {
got_type: cond_type.into()
});
}
self.fill_statements(helper_fns, if_block, expected_return_type, arena)?;
if !else_block.is_empty() {
if *is_chained {
debug_assert!(else_block.len() == 1);
let [statement] = else_block else {unreachable!()};
(condition, is_chained, if_block, else_block) = match statement {
Statement::If{condition, is_chained, if_block, else_block} => (condition, is_chained, if_block, else_block),
_ => unreachable!(),
};
continue;
} else {
self.fill_statements(helper_fns, else_block, expected_return_type, arena)?;
}
}
break;
}
}
Statement::While {
condition,
block,
} => {
let cond_type = self.fill_expr(helper_fns, condition, arena)?;
if cond_type != GrugType::Bool {
return Err(TypePropogatorError::WhileConditionTypeMismatch {
got_type: cond_type.into()
});
}
self.num_while_loops_deep += 1;
self.fill_statements(helper_fns, block, expected_return_type, arena)?;
self.num_while_loops_deep -= 1;
}
Statement::Return {
expr,
} => {
let return_ty = expr.as_mut()
.map(|expr| self.fill_expr(helper_fns, expr, arena))
.unwrap_or(Ok(GrugType::Void))?;
if *expected_return_type != (GrugType::Id{custom_name: None}) && *expected_return_type != return_ty {
return Err(TypePropogatorError::MismatchedReturnType{
function_name: Arc::from(self.current_fn_name.unwrap()),
expected_type: expected_return_type.into(),
got_type: return_ty.into(),
})
}
}
Statement::Break => {
if self.num_while_loops_deep == 0 {
return Err(TypePropogatorError::BreakStatementOutsideWhileLoop);
}
}
Statement::Continue => {
if self.num_while_loops_deep == 0 {
return Err(TypePropogatorError::ContinueStatementOutsideWhileLoop);
}
}
_ => (),
}
}
self.pop_scope();
Ok(())
}
fn check_global_expr(&mut self, assignment_expr: &Expr<'_>, name: &str) -> Result<(), TypePropogatorError> {
match &assignment_expr.data {
ExprData::Entity(_) => unreachable!(),
ExprData::Resource(_) => unreachable!(),
ExprData::True |
ExprData::False |
ExprData::String(_) |
ExprData::Identifier(_) |
ExprData::Number(_, _) => (),
ExprData::Unary{
op: _,
expr,
} => self.check_global_expr(expr, name)?,
ExprData::Binary{
left,
right,
op: _,
} => {
self.check_global_expr(left, name)?;
self.check_global_expr(right, name)?;
},
ExprData::Call{
name: fn_name,
args,
ptr : _,
} => {
if fn_name.to_str().starts_with("helper_") {
Err(TypePropogatorError::GlobalCantCallHelperFn{
global_name: Arc::from(name),
})?;
}
args.iter().map(|argument| self.check_global_expr(argument, name))
.collect::<Result<Vec<_>, _>>()?;
},
ExprData::Parenthesized(expr) => self.check_global_expr(expr, name)?,
}
Ok(())
}
fn fill_expr(&mut self, helper_fns: &[(&str, (GrugType<'arena>, &[Argument<'arena>]))], assignment_expr: &mut Expr<'arena>, arena: &'arena Arena) -> Result<GrugType<'arena>, TypePropogatorError> {
assert!(assignment_expr.result_type.is_none());
let result_ty = match &mut assignment_expr.data {
ExprData::True => GrugType::Bool,
ExprData::False => GrugType::Bool,
ExprData::String{
..
} => GrugType::String,
ExprData::Resource{..} | ExprData::Entity{..} => {
panic!("Cannot encounter resource or entity string when filling expression");
}
ExprData::Identifier(name) => {
let ty = self.get_variable_type(name.to_str()).ok_or_else(|| TypePropogatorError::VariableDoesNotExist{
name: Arc::from(name.to_str()),
})?;
ty
},
ExprData::Number{
..
} => GrugType::Number,
ExprData::Unary{
op: operator,
expr,
} => {
if let Expr{data: ExprData::Unary{op: next_operator, ..}, ..} = expr && next_operator == operator {
return Err(TypePropogatorError::AdjacentUnaryOperators{
operator: *operator,
});
}
let result_ty = self.fill_expr(helper_fns, expr, arena)?;
match (operator, &result_ty) {
(UnaryOperator::Not, GrugType::Bool) => (),
(UnaryOperator::Not, got) => return Err(TypePropogatorError::NotOperatorNotBeforeBool{
got: got.into(),
}),
(UnaryOperator::Minus, GrugType::Number) => (),
(UnaryOperator::Minus, got) => return Err(TypePropogatorError::MinusOperatorNotBeforeNumber{
got: got.into(),
}),
};
result_ty
},
ExprData::Binary{
left,
right,
op,
} => {
let result_0 = self.fill_expr(helper_fns, left, arena)?;
let result_1 = self.fill_expr(helper_fns, right, arena)?;
match (&result_1, *op) {
(GrugType::String, BinaryOperator::DoubleEquals) |
(GrugType::String, BinaryOperator::NotEquals) => (),
(GrugType::String, _) => {
return Err(TypePropogatorError::CannotCompareStrings{
operator: *op
});
},
_ => (),
}
if !GrugType::match_non_exact(&result_0, &result_1) {
return Err(TypePropogatorError::BinaryOperatorTypeMismatch{
operator: *op,
left: result_0.into(),
right: result_1.into(),
});
}
match op {
BinaryOperator::Or | BinaryOperator::And => {
if result_0 != GrugType::Bool {
return Err(TypePropogatorError::LogicalOperatorExpectsBool {
operator: *op,
});
}
GrugType::Bool
}
BinaryOperator::DoubleEquals | BinaryOperator::NotEquals => {
GrugType::Bool
},
BinaryOperator::Greater | BinaryOperator::GreaterEquals |
BinaryOperator::Less | BinaryOperator::LessEquals => {
if result_0 != GrugType::Number {
return Err(TypePropogatorError::ComparisonOperatorExpectsNumber {
operator: *op,
got_type: result_0.into(),
});
}
GrugType::Bool
},
BinaryOperator::Plus | BinaryOperator::Minus |
BinaryOperator::Multiply | BinaryOperator::Division => {
if result_0 != GrugType::Number {
return Err(TypePropogatorError::ArithmeticOperatorExpectsNumber {
operator: *op,
got_type: result_0.into(),
});
}
result_0
},
BinaryOperator::Remainder => {
if result_0 != GrugType::Number {
return Err(TypePropogatorError::RemainderOperatorExpectsNumber {
got_ty: result_0.into(),
});
}
result_0
},
}
},
ExprData::Call{
name: fn_name,
args,
ptr ,
} => {
let fn_name = fn_name.to_str();
args.iter_mut().map(|argument| self.fill_expr(helper_fns, argument, arena)).collect::<Result<Vec<_>, _>>()?;
if let Some((_, (return_ty, sig_arguments))) = helper_fns.iter().find(|(name, _)| *name == fn_name) {
self.check_arguments(fn_name, sig_arguments, args, arena)?;
return_ty.clone()
} else if let Some(game_fn) = self.game_fns.get(fn_name) {
self.check_arguments(fn_name, &game_fn.arguments, args, arena)?;
if let Some(game_fn_ptr) = self.game_fn_ptrs.get(fn_name) {
*ptr = Some(*game_fn_ptr);
game_fn.return_ty
} else {
return Err(TypePropogatorError::GameFunctionNotProvided{
fn_name: String::from(fn_name),
});
}
} else if fn_name.starts_with("on_") {
return Err(TypePropogatorError::CallOnFnWithinOnFn {
on_fn_name: Arc::from(fn_name)
});
} else {
return Err(TypePropogatorError::FunctionDoesNotExist {
function_name: Arc::from(fn_name)
});
}
},
ExprData::Parenthesized(expr) => {
self.fill_expr(helper_fns, expr, arena)?
},
};
assignment_expr.result_type = Some(Box::leak(Box::new_in(result_ty, arena)));
Ok(result_ty)
}
fn check_arguments(&mut self, function_name: &str, signature: &[Argument<'_>], arguments: &mut [Expr<'arena>], arena: &'arena Arena) -> Result<(), TypePropogatorError> {
debug_assert!(arguments.iter().all(|arg| arg.result_type.is_some()));
if signature.len() > arguments.len() {
return Err(TypePropogatorError::TooFewArguments{
function_name: Arc::from(function_name),
expected_name: Arc::from(signature[arguments.len()].name.to_str()),
expected_type: signature[arguments.len()].ty.into(),
});
} else if signature.len() < arguments.len() {
return Err(TypePropogatorError::TooManyArguments{
function_name: Arc::from(function_name),
got_type: arguments[signature.len()].result_type.as_deref().unwrap().into(),
});
}
for (param, arg) in signature.iter().zip(arguments) {
if let GrugType::Resource{extension} = param.ty
&& let ExprData::String(ref mut value) = arg.data {
let value_ntstr = value.to_ntstr();
self.validate_resource_string(value_ntstr.as_str(), extension.to_str())?;
*value = self.fix_resource_string(value_ntstr, arena).as_ntstrptr();
} else if let GrugType::Entity{entity_type: _} = param.ty
&& let ExprData::String(ref mut value) = arg.data {
let value_ntstr = value.to_ntstr();
self.validate_entity_string(value_ntstr.as_str())?;
if let Some(fixed_entity) = self.fix_entity_string(value_ntstr, arena) {*value = fixed_entity.as_ntstrptr()}
} else if *arg.result_type.as_ref().unwrap() == &GrugType::Void {
return Err(TypePropogatorError::VoidArgumentInFunctionCall{
function_name: Arc::from(function_name),
signature_type: param.ty.into(),
parameter_name: Arc::from(param.name.to_str()),
});
} else if let GrugType::Id{custom_name: None} = param.ty && let Some(GrugType::Id{custom_name: _}) = arg.result_type {
arg.result_type = Some(Box::leak(Box::new_in(GrugType::Id{custom_name: None}, arena)));
} else if Some(¶m.ty) != arg.result_type.as_deref() {
return Err(TypePropogatorError::FunctionArgumentMismatch {
function_name: Arc::from(function_name),
expected_type: param.ty.into(),
got_type: arg.result_type.as_deref().unwrap().into(),
parameter_name: Arc::from(param.name.to_str()),
});
}
}
Ok(())
}
fn validate_resource_string(&mut self, value: &str, extension: &str) -> Result<(), ResourceValidationError> {
if value.is_empty() {
Err(ResourceValidationError::EmptyResource{ })
} else if value.starts_with("/") {
Err(ResourceValidationError::LeadingForwardSlash {
value: Arc::from(value),
})
} else if value.ends_with("/") {
Err(ResourceValidationError::TrailingForwardSlash {
value: Arc::from(value),
})
} else if value.contains("\\") {
Err(ResourceValidationError::ContainsBackslash {
value: Arc::from(value),
})
} else if value.contains("//") {
Err(ResourceValidationError::ContainsDoubleForwardSlash {
value: Arc::from(value),
})
} else if value == ".." || value.starts_with("../") {
Err(ResourceValidationError::BeginsWithDotDotWithoutSlash {
value: Arc::from(value),
})
} else if value.ends_with("/..") || value.contains("/../") {
Err(ResourceValidationError::ContainsSlashDotDotInMiddle {
value: Arc::from(value),
})
} else if value == "." || value.starts_with("./") {
Err(ResourceValidationError::BeginsWithDotWithoutSlash {
value: Arc::from(value),
})
} else if value.ends_with("/.") || value.contains("/./") {
Err(ResourceValidationError::ContainsSlashDotInMiddle {
value: Arc::from(value),
})
} else if value.ends_with(".") {
Err(ResourceValidationError::EndsWithDot {
value: Arc::from(value),
})
} else if value.ends_with(extension) {
Ok(())
} else {
Err(ResourceValidationError::ExtensionMismatch {
expected: Arc::from(extension),
value: Arc::from(value),
})
}
}
fn fix_resource_string(&mut self, value: &NTStr, arena: &'arena Arena) -> &'arena NTStr {
Box::leak(NTStr::box_from_str_in(&format!("{}/{}", self.current_mod_name, value), arena))
}
fn validate_entity_string(&mut self, entity_string: &str) -> Result<(), EntityValidationError> {
if entity_string.is_empty() {
return Err(EntityValidationError::EntityCantBeEmpty);
}
let (mod_name, entity_name) = if let Some((mod_name, entity_name)) = entity_string.split_once(":") {
if mod_name.is_empty() {
return Err(EntityValidationError::EntityMissingModName {
entity_string: Arc::from(entity_string),
});
}
if entity_name.is_empty() {
return Err(EntityValidationError::EntityMissingEntityName {
mod_name: String::from(mod_name),
entity_string: Arc::from(entity_string),
});
}
if mod_name == self.current_mod_name {
return Err(EntityValidationError::ModNameIsCurrentMod {
full_entity_string: Arc::from(entity_string),
mod_name: String::from(mod_name),
entity_name: String::from(entity_name),
});
}
(mod_name, entity_name)
} else {
("", entity_string)
};
if let Some(ch) = mod_name.chars().find(|ch| !(ch.is_ascii_lowercase() || ch.is_ascii_digit() || *ch == '_' || *ch == '-')) {
return Err(EntityValidationError::ModNameHasInvalidCharacter{
entity_name: Arc::from(entity_string),
invalid_char: ch,
});
}
if let Some(ch) = entity_name.chars().find(|ch| !(ch.is_ascii_lowercase() || ch.is_ascii_digit() || *ch == '_' || *ch == '-')) {
return Err(EntityValidationError::EntityNameHasInvalidCharacter{
entity_name: Arc::from(entity_string),
invalid_char: ch,
});
}
Ok(())
}
fn fix_entity_string(&mut self, value: &NTStr, arena: &'arena Arena) -> Option<&'arena NTStr> {
if value.split_once(":").is_none() {
Some(Box::leak(NTStr::box_from_str_in(&format!("{}:{}", self.current_mod_name, value), arena)))
} else {
None
}
}
fn get_variable_type(&self, var_name: &str) -> Option<GrugType<'arena>> {
if let var@Some(_) = self.get_local_variable_type(var_name) {
var
} else {
self.get_global_variable_type(var_name)
}
}
fn push_scope(&mut self) {
self.local_variables.push(HashMap::new());
}
fn pop_scope(&mut self) {
self.local_variables.pop().unwrap();
}
fn get_local_variable_type(&self, var_name: &str) -> Option<GrugType<'arena>> {
for scope in self.local_variables.iter().rev() {
if let var@Some(_) = scope.get(var_name) {
return var.cloned();
}
}
None
}
fn get_global_variable_type(&self, var_name: &str) -> Option<GrugType<'arena>> {
self.global_variables.get(var_name).cloned()
}
fn add_local_variable(&mut self, name: &'arena str, ty: GrugType<'arena>) -> Result<(), TypePropogatorError> {
if self.get_global_variable_type(&name).is_some() {
return Err(TypePropogatorError::LocalVariableShadowedByGlobal{
name: Arc::from(name),
});
}
match self.local_variables.last_mut().expect("There is no local scope to push onto").entry(name) {
Entry::Occupied(_) => return Err(TypePropogatorError::LocalVariableShadowedByLocal{
name: Arc::from(name),
})?,
Entry::Vacant(x) => {x.insert(ty);},
}
Ok(())
}
fn add_global_variable(&mut self, name: &'arena str, ty: GrugType<'arena>) -> Result<(), TypePropogatorError> {
match self.global_variables.entry(name) {
Entry::Occupied(_) => return Err(TypePropogatorError::GlobalVariableShadowed{
name: Arc::from(name),
})?,
Entry::Vacant(x) => {x.insert(ty);},
}
Ok(())
}
}