use std::collections::HashMap;
use std::path::Path;
use std::sync::Arc;
use crate::ntstring::NTStr;
use crate::ast::{Argument, GrugType};
use crate::arena::Arena;
use allocator_api2::boxed::Box;
pub struct ModApi {
entities: HashMap<Arc<str>, ModApiEntity<'static>>,
game_functions: HashMap<Arc<str>, ModApiGameFn<'static>>,
_arena: Arena,
}
impl ModApi {
pub fn entities<'a>(&'a self) -> &'a HashMap<Arc<str>, ModApiEntity<'a>> {
&self.entities
}
pub fn game_functions<'a>(&'a self) -> &'a HashMap<Arc<str>, ModApiGameFn<'a>> {
&self.game_functions
}
}
#[derive(Debug)]
pub struct ModApiEntity<'a> {
#[allow(dead_code)]
pub(crate) description: Option<String>,
pub(crate) on_fns: Vec<(Arc<str>, ModApiOnFn<'a>)>,
}
impl<'a> ModApiEntity<'a> {
pub fn get_on_fn(&self, name: &str) -> Option<(usize, &ModApiOnFn<'_>)> {
self.on_fns.iter().enumerate().find_map(|(i, (fn_name, func))| (name == &**fn_name).then_some((i, func)))
}
}
#[derive(Debug)]
pub struct ModApiOnFn<'a> {
#[allow(dead_code)]
pub(super) description: Option<String>,
pub(super) arguments: Vec<Argument<'a>>,
}
#[derive(Debug)]
pub struct ModApiGameFn<'a> {
#[allow(dead_code)]
pub(super) description: Option<String>,
pub(super) return_ty: GrugType<'a>,
pub(super) arguments: Vec<Argument<'a>>,
}
#[derive(Debug)]
pub enum ModApiError{
JsonError(json::Error),
IOError(std::io::Error),
EntitiesNotObject,
OnFunctionsNotObject{
entity_name: String,
},
OnFnsArgumentsNotArray{
entity_name: String,
on_fn_name: String,
},
OnFnArgumentMissingName{
entity_name: String,
on_fn_name: String,
},
OnFnArgumentMissingType{
entity_name: String,
on_fn_name: String,
},
OnFnArgumentResource {
entity_name: String,
on_fn_name: String,
argument_name: String,
},
OnFnArgumentEntity {
entity_name: String,
on_fn_name: String,
argument_name: String,
},
GameFnsNotObject,
OnFnArgumentVoid {
entity_name: String,
on_fn_name: String,
argument_name: String,
},
GameFnArgumentsNotArray{
game_fn_name: String,
},
GameFnArgumentMissingName{
game_fn_name: String,
},
GameFnArgumentMissingType{
game_fn_name: String,
argument_name: String,
},
GameFnArgumentVoid{
game_fn_name: String,
argument_name: String,
},
GameFnResourceMissingExtension{
game_fn_name: String,
argument_name: String,
},
GameFnEntityMissingType{
game_fn_name: String,
argument_name: String,
},
GameFnReturnsEntity{
game_fn_name: String,
},
GameFnReturnsResource{
game_fn_name: String,
},
GameFnNotProvided {
game_fn_name: String,
}
}
impl From<json::Error> for ModApiError {
fn from(other: json::Error) -> ModApiError {
ModApiError::JsonError(other)
}
}
impl From<std::io::Error> for ModApiError {
fn from(other: std::io::Error) -> ModApiError {
ModApiError::IOError(other)
}
}
pub fn get_mod_api<P: AsRef<Path>>(mod_api_path: P) -> Result<ModApi, ModApiError> {
let mod_api_text = std::fs::read_to_string(mod_api_path)?;
get_mod_api_from_text(&mod_api_text)
}
pub fn get_mod_api_from_text(mod_api_text: &str) -> Result<ModApi, ModApiError> {
let arena = Arena::new();
let mod_api_json = json::parse(mod_api_text)?;
let entities = &mod_api_json["entities"];
if !entities.is_object() {
return Err(ModApiError::EntitiesNotObject);
}
let entities = entities.entries().map(|(entity_name, entity_values)| {
let description = entity_values["description"].as_str().map(str::to_string);
let on_fns = &entity_values["on_functions"];
if !on_fns.is_object() && !on_fns.is_null() {
return Err(ModApiError::OnFunctionsNotObject{
entity_name: entity_name.to_string(),
});
}
let on_fns = on_fns.entries().map(|(fn_name, fn_values)| {
let description = fn_values["description"].as_str().map(str::to_string);
let arguments = &fn_values["arguments"];
if !arguments.is_array() && !arguments.is_null(){
return Err(ModApiError::OnFnsArgumentsNotArray{
entity_name: entity_name.to_string(),
on_fn_name: fn_name.to_string(),
});
}
let arguments = arguments.members().map(|argument_values| {
let argument_name = argument_values["name"].as_str().ok_or(ModApiError::OnFnArgumentMissingName{
entity_name: entity_name.to_string(),
on_fn_name: fn_name.to_string(),
})?;
let argument_name = Box::leak(NTStr::box_from_str_in(argument_name, &arena));
let ty = argument_values["type"].as_str().ok_or(ModApiError::OnFnArgumentMissingType{
entity_name: entity_name.to_string(),
on_fn_name: fn_name.to_string(),
})?;
let ty = match ty {
"void" => Err(ModApiError::OnFnArgumentVoid{
entity_name: entity_name.to_string(),
on_fn_name: fn_name.to_string(),
argument_name: argument_name.to_string(),
})?,
"bool" => GrugType::Bool,
"number" => GrugType::Number,
"string" => GrugType::String,
"id" => GrugType::Id{custom_name: None},
"resource" => Err(ModApiError::OnFnArgumentResource{
entity_name: entity_name.to_string(),
on_fn_name: fn_name.to_string(),
argument_name: argument_name.to_string(),
})?,
"entity" => Err(ModApiError::OnFnArgumentEntity{
entity_name: entity_name.to_string(),
on_fn_name: fn_name.to_string(),
argument_name: argument_name.to_string(),
})?,
type_name => {
let extra_value = Box::leak(NTStr::box_from_str_in(type_name, &arena));
GrugType::Id {
custom_name: Some(extra_value.as_ntstrptr()),
}
}
};
Ok(Argument{
name: unsafe{argument_name.as_ntstrptr().detach_lifetime()},
ty,
})
}).collect::<Result<Vec<_>, ModApiError>>()?;
let fn_name = Arc::from(fn_name);
Ok((Arc::clone(&fn_name), ModApiOnFn{
description,
arguments,
}))
}).collect::<Result<Vec<_>, _>>()?;
Ok((Arc::from(entity_name), ModApiEntity{
description,
on_fns
}))
}).collect::<Result<HashMap<_, _>, ModApiError>>()?;
let game_functions = &mod_api_json["game_functions"];
if !game_functions.is_object() {
return Err(ModApiError::GameFnsNotObject);
}
let game_functions = game_functions.entries().map(|(fn_name, game_fn_values)| {
let description = game_fn_values["description"].as_str().map(str::to_string);
let arguments = &game_fn_values["arguments"];
if !arguments.is_array() && !arguments.is_null(){
return Err(ModApiError::GameFnArgumentsNotArray{
game_fn_name: fn_name.to_string(),
});
}
let arguments = arguments.members().map(|argument_values| {
let argument_name = argument_values["name"].as_str().ok_or(ModApiError::GameFnArgumentMissingName{
game_fn_name: fn_name.to_string(),
})?;
let argument_name = Box::leak(NTStr::box_from_str_in(argument_name, &arena));
let ty = argument_values["type"].as_str().ok_or(ModApiError::GameFnArgumentMissingType{
game_fn_name: fn_name.to_string(),
argument_name: argument_name.to_string(),
})?;
let ty = match ty {
"void" => Err(ModApiError::GameFnArgumentVoid{
game_fn_name: fn_name.to_string(),
argument_name: argument_name.to_string(),
})?,
"bool" => GrugType::Bool,
"number" => GrugType::Number,
"string" => GrugType::String,
"id" => GrugType::Id{custom_name: None},
"entity" => {
let entity_type = argument_values["entity_type"].as_str().ok_or(ModApiError::GameFnEntityMissingType{
game_fn_name: fn_name.to_string(),
argument_name: argument_name.to_string(),
})?;
GrugType::Entity {
entity_type: (!entity_type.is_empty()).then(|| {
let entity_type = Box::leak(NTStr::box_from_str_in(entity_type, &arena));
entity_type.as_ntstrptr()
})
}
},
"resource" => {
let extension = argument_values["resource_extension"].as_str().ok_or(ModApiError::GameFnResourceMissingExtension{
game_fn_name: fn_name.to_string(),
argument_name: argument_name.to_string(),
})?;
let extension = Box::leak(NTStr::box_from_str_in(extension, &arena)).as_ntstrptr();
GrugType::Resource {
extension
}
}
type_name => {
let extra_value = Box::leak(NTStr::box_from_str_in(type_name, &arena)).as_ntstrptr();
GrugType::Id {
custom_name: Some(extra_value),
}
}
};
Ok(Argument{
name: unsafe{argument_name.as_ntstrptr().detach_lifetime()},
ty,
})
}).collect::<Result<Vec<_>, ModApiError>>()?;
let return_ty = game_fn_values["return_type"].as_str().unwrap_or("void");
let return_ty = match return_ty {
"void" => GrugType::Void,
"bool" => GrugType::Bool,
"number" => GrugType::Number,
"string" => GrugType::String,
"id" => GrugType::Id{custom_name: None},
"entity" => Err(ModApiError::GameFnReturnsEntity{
game_fn_name: fn_name.to_string(),
})?,
"resource" => Err(ModApiError::GameFnReturnsResource{
game_fn_name: fn_name.to_string(),
})?,
type_name => {
let extra_value = Box::leak(NTStr::box_from_str_in(type_name, &arena)).as_ntstrptr();
GrugType::Id {
custom_name: Some(extra_value),
}
}
};
let fn_name = Arc::from(fn_name);
Ok((Arc::clone(&fn_name), ModApiGameFn{
return_ty,
description,
arguments
}))
}).collect::<Result<HashMap<_, _>, ModApiError>>()?;
Ok(ModApi{
entities: unsafe{std::mem::transmute::<HashMap<Arc<str>, ModApiEntity<'_>>, _>(entities)},
game_functions: unsafe{std::mem::transmute::<HashMap<Arc<str>, ModApiGameFn<'_>>, _>(game_functions)},
_arena: arena,
})
}