refactor(vm): pull generated data into new crate
pregenerate prefab templates -> locked behind crate feature
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
use color_eyre::eyre;
|
||||
use quote::ToTokens;
|
||||
|
||||
use std::{collections::BTreeMap, process::Command};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::{enums::Enums, stationpedia::Stationpedia};
|
||||
|
||||
@@ -12,6 +13,7 @@ mod utils;
|
||||
pub fn generate(
|
||||
stationpedia_path: &std::path::Path,
|
||||
workspace: &std::path::Path,
|
||||
modules: &[&str],
|
||||
) -> color_eyre::Result<()> {
|
||||
let mut pedia: Stationpedia = parse_json(&mut serde_json::Deserializer::from_reader(
|
||||
std::io::BufReader::new(std::fs::File::open(
|
||||
@@ -37,21 +39,41 @@ pub fn generate(
|
||||
std::io::BufReader::new(std::fs::File::open(stationpedia_path.join("Enums.json"))?),
|
||||
))?;
|
||||
|
||||
database::generate_database(&pedia, &enums, workspace)?;
|
||||
let enums_files = enums::generate_enums(&pedia, &enums, workspace)?;
|
||||
let inst_files = instructions::generate_instructions(&pedia, workspace)?;
|
||||
let mut generated_files = Vec::new();
|
||||
if modules.contains(&"enums") {
|
||||
if modules.len() > 1 {
|
||||
eprintln!(
|
||||
"generating enums alone, recompile the xtask and run again with other modules."
|
||||
)
|
||||
} else {
|
||||
eprintln!("generating enums...");
|
||||
}
|
||||
|
||||
let generated_files = [enums_files.as_slice(), inst_files.as_slice()].concat();
|
||||
let enums_files = enums::generate_enums(&pedia, &enums, workspace)?;
|
||||
eprintln!("Formatting generated files...");
|
||||
for file in &enums_files {
|
||||
prepend_generated_comment_and_format(file)?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if modules.contains(&"database") {
|
||||
eprintln!("generating database...");
|
||||
|
||||
let database_files = database::generate_database(&pedia, &enums, workspace)?;
|
||||
generated_files.extend(database_files);
|
||||
}
|
||||
|
||||
if modules.contains(&"instructions") {
|
||||
eprintln!("generating instructions...");
|
||||
let inst_files = instructions::generate_instructions(&pedia, workspace)?;
|
||||
generated_files.extend(inst_files);
|
||||
}
|
||||
|
||||
eprintln!("Formatting generated files...");
|
||||
for file in &generated_files {
|
||||
prepend_generated_comment(file)?;
|
||||
prepend_generated_comment_and_format(file)?;
|
||||
}
|
||||
let mut cmd = Command::new("cargo");
|
||||
cmd.current_dir(workspace);
|
||||
cmd.arg("fmt").arg("--");
|
||||
cmd.args(&generated_files);
|
||||
cmd.status()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -71,29 +93,34 @@ pub fn parse_json<'a, T: serde::Deserialize<'a>>(
|
||||
})
|
||||
}
|
||||
|
||||
fn prepend_generated_comment(file_path: &std::path::Path) -> color_eyre::Result<()> {
|
||||
fn format_rust(content: impl ToTokens) -> color_eyre::Result<String> {
|
||||
let content = syn::parse2(content.to_token_stream())?;
|
||||
Ok(prettyplease::unparse(&content))
|
||||
}
|
||||
|
||||
fn prepend_generated_comment_and_format(file_path: &std::path::Path) -> color_eyre::Result<()> {
|
||||
use std::io::Write;
|
||||
let tmp_path = file_path.with_extension("rs.tmp");
|
||||
{
|
||||
let mut tmp = std::fs::File::create(&tmp_path)?;
|
||||
let mut src = std::fs::File::open(file_path)?;
|
||||
write!(
|
||||
&mut tmp,
|
||||
"// ================================================= \n\
|
||||
// !! <-----> DO NOT MODIFY <-----> !! \n\
|
||||
// \n\
|
||||
// This module was automatically generated by an
|
||||
// xtask \n\
|
||||
// \n\
|
||||
// run `cargo xtask generate` from the workspace \n\
|
||||
// to regenerate \n\
|
||||
// \n\
|
||||
// ================================================= \n\
|
||||
\n\
|
||||
\n\
|
||||
"
|
||||
)?;
|
||||
std::io::copy(&mut src, &mut tmp)?;
|
||||
let src = syn::parse_file(&std::fs::read_to_string(file_path)?)?;
|
||||
|
||||
let formated = format_rust(quote::quote! {
|
||||
// =================================================
|
||||
// !! <-----> DO NOT MODIFY <-----> !!
|
||||
//
|
||||
// This module was automatically generated by an
|
||||
// xtask
|
||||
//
|
||||
// run `cargo xtask generate` from the workspace
|
||||
// to regenerate
|
||||
//
|
||||
// =================================================
|
||||
|
||||
#src
|
||||
})?;
|
||||
|
||||
write!(&mut tmp, "{formated}")?;
|
||||
}
|
||||
std::fs::remove_file(file_path)?;
|
||||
std::fs::rename(&tmp_path, file_path)?;
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
use std::{collections::BTreeMap, io::Write};
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
io::{BufWriter, Write},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use quote::quote;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
@@ -7,11 +12,18 @@ use crate::{
|
||||
stationpedia::{self, Page, Stationpedia},
|
||||
};
|
||||
|
||||
use stationeers_data::templates::{
|
||||
ConnectionInfo, DeviceInfo, Instruction, ItemInfo, ItemLogicMemoryTemplate, ItemLogicTemplate,
|
||||
ItemSlotsTemplate, ItemTemplate, LogicInfo, MemoryInfo, ObjectTemplate, PrefabInfo, SlotInfo,
|
||||
StructureInfo, StructureLogicDeviceMemoryTemplate, StructureLogicDeviceTemplate,
|
||||
StructureLogicTemplate, StructureSlotsTemplate, StructureTemplate,
|
||||
};
|
||||
|
||||
pub fn generate_database(
|
||||
stationpedia: &stationpedia::Stationpedia,
|
||||
enums: &enums::Enums,
|
||||
workspace: &std::path::Path,
|
||||
) -> color_eyre::Result<()> {
|
||||
) -> color_eyre::Result<Vec<PathBuf>> {
|
||||
let templates = generate_templates(stationpedia)?;
|
||||
|
||||
eprintln!("Writing prefab database ...");
|
||||
@@ -104,6 +116,58 @@ pub fn generate_database(
|
||||
let mut database_file = std::io::BufWriter::new(std::fs::File::create(database_path)?);
|
||||
serde_json::to_writer(&mut database_file, &db)?;
|
||||
database_file.flush()?;
|
||||
|
||||
let prefab_map_path = workspace
|
||||
.join("stationeers_data")
|
||||
.join("src")
|
||||
.join("database")
|
||||
.join("prefab_map.rs");
|
||||
let mut prefab_map_file = std::io::BufWriter::new(std::fs::File::create(&prefab_map_path)?);
|
||||
write_prefab_map(&mut prefab_map_file, &db.prefabs)?;
|
||||
|
||||
Ok(vec![prefab_map_path])
|
||||
}
|
||||
|
||||
fn write_prefab_map<T: std::io::Write>(
|
||||
writer: &mut BufWriter<T>,
|
||||
prefabs: &BTreeMap<String, ObjectTemplate>,
|
||||
) -> color_eyre::Result<()> {
|
||||
write!(
|
||||
writer,
|
||||
"{}",
|
||||
quote! {
|
||||
use crate::enums::script_enums::*;
|
||||
use crate::enums::basic_enums::*;
|
||||
use crate::enums::{MemoryAccess, ConnectionType, ConnectionRole};
|
||||
use crate::templates::*;
|
||||
}
|
||||
)?;
|
||||
let entries = prefabs
|
||||
.values()
|
||||
.map(|prefab| {
|
||||
let hash = prefab.prefab().prefab_hash;
|
||||
let obj = syn::parse_str::<syn::Expr>(&uneval::to_string(prefab)?)?;
|
||||
let entry = quote! {
|
||||
(
|
||||
#hash,
|
||||
#obj.into(),
|
||||
)
|
||||
};
|
||||
Ok(entry)
|
||||
})
|
||||
.collect::<Result<Vec<_>, color_eyre::Report>>()?;
|
||||
write!(
|
||||
writer,
|
||||
"{}",
|
||||
quote! {
|
||||
pub fn build_prefab_database() -> std::collections::BTreeMap<i32, crate::templates::ObjectTemplate> {
|
||||
#[allow(clippy::unreadable_literal)]
|
||||
std::collections::BTreeMap::from([
|
||||
#(#entries),*
|
||||
])
|
||||
}
|
||||
},
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -360,7 +424,10 @@ fn slot_inserts_to_info(slots: &[stationpedia::SlotInsert]) -> Vec<SlotInfo> {
|
||||
tmp.iter()
|
||||
.map(|slot| SlotInfo {
|
||||
name: slot.slot_name.clone(),
|
||||
typ: slot.slot_type.clone(),
|
||||
typ: slot
|
||||
.slot_type
|
||||
.parse()
|
||||
.unwrap_or_else(|err| panic!("faild to parse slot class: {err}")),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -386,73 +453,45 @@ pub struct ObjectDatabase {
|
||||
pub logicable_items: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum ObjectTemplate {
|
||||
Structure(StructureTemplate),
|
||||
StructureSlots(StructureSlotsTemplate),
|
||||
StructureLogic(StructureLogicTemplate),
|
||||
StructureLogicDevice(StructureLogicDeviceTemplate),
|
||||
StructureLogicDeviceMemory(StructureLogicDeviceMemoryTemplate),
|
||||
Item(ItemTemplate),
|
||||
ItemSlots(ItemSlotsTemplate),
|
||||
ItemLogic(ItemLogicTemplate),
|
||||
ItemLogicMemory(ItemLogicMemoryTemplate),
|
||||
}
|
||||
|
||||
impl ObjectTemplate {
|
||||
fn prefab(&self) -> &PrefabInfo {
|
||||
use ObjectTemplate::*;
|
||||
match self {
|
||||
Structure(s) => &s.prefab,
|
||||
StructureSlots(s) => &s.prefab,
|
||||
StructureLogic(s) => &s.prefab,
|
||||
StructureLogicDevice(s) => &s.prefab,
|
||||
StructureLogicDeviceMemory(s) => &s.prefab,
|
||||
Item(i) => &i.prefab,
|
||||
ItemSlots(i) => &i.prefab,
|
||||
ItemLogic(i) => &i.prefab,
|
||||
ItemLogicMemory(i) => &i.prefab,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PrefabInfo {
|
||||
pub prefab_name: String,
|
||||
pub prefab_hash: i32,
|
||||
pub desc: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SlotInfo {
|
||||
pub name: String,
|
||||
pub typ: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LogicInfo {
|
||||
pub logic_slot_types: BTreeMap<u32, stationpedia::LogicSlotTypes>,
|
||||
pub logic_types: stationpedia::LogicTypes,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub modes: Option<BTreeMap<u32, String>>,
|
||||
pub transmission_receiver: bool,
|
||||
pub wireless_logic: bool,
|
||||
pub circuit_holder: bool,
|
||||
}
|
||||
|
||||
impl From<&stationpedia::LogicInfo> for LogicInfo {
|
||||
fn from(value: &stationpedia::LogicInfo) -> Self {
|
||||
LogicInfo {
|
||||
logic_slot_types: value.logic_slot_types.clone(),
|
||||
logic_types: value.logic_types.clone(),
|
||||
logic_slot_types: value
|
||||
.logic_slot_types
|
||||
.iter()
|
||||
.map(|(index, slt_map)| {
|
||||
(
|
||||
*index,
|
||||
slt_map
|
||||
.slot_types
|
||||
.iter()
|
||||
.map(|(key, val)| {
|
||||
(
|
||||
key.parse().unwrap_or_else(|err| {
|
||||
panic!("failed to parse logic slot type: {err}")
|
||||
}),
|
||||
val.parse().unwrap_or_else(|err| {
|
||||
panic!("failed to parse memory access: {err}")
|
||||
}),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
logic_types: value
|
||||
.logic_types
|
||||
.types
|
||||
.iter()
|
||||
.map(|(key, val)| {
|
||||
(
|
||||
key.parse()
|
||||
.unwrap_or_else(|err| panic!("failed to parse logic type: {err}")),
|
||||
val.parse()
|
||||
.unwrap_or_else(|err| panic!("failed to parse memory access: {err}")),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
modes: None,
|
||||
transmission_receiver: false,
|
||||
wireless_logic: false,
|
||||
@@ -461,63 +500,32 @@ impl From<&stationpedia::LogicInfo> for LogicInfo {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ItemInfo {
|
||||
pub consumable: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub filter_type: Option<String>,
|
||||
pub ingredient: bool,
|
||||
pub max_quantity: u32,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub reagents: Option<BTreeMap<String, f64>>,
|
||||
pub slot_class: String,
|
||||
pub sorting_class: String,
|
||||
}
|
||||
|
||||
impl From<&stationpedia::Item> for ItemInfo {
|
||||
fn from(item: &stationpedia::Item) -> Self {
|
||||
ItemInfo {
|
||||
consumable: item.consumable.unwrap_or(false),
|
||||
filter_type: item.filter_type.clone(),
|
||||
filter_type: item.filter_type.as_ref().map(|typ| {
|
||||
typ.parse()
|
||||
.unwrap_or_else(|err| panic!("failed to parse filter type: {err}"))
|
||||
}),
|
||||
ingredient: item.ingredient.unwrap_or(false),
|
||||
max_quantity: item.max_quantity.unwrap_or(1.0) as u32,
|
||||
reagents: item
|
||||
.reagents
|
||||
.as_ref()
|
||||
.map(|map| map.iter().map(|(key, val)| (key.clone(), *val)).collect()),
|
||||
slot_class: item.slot_class.clone(),
|
||||
sorting_class: item.sorting_class.clone(),
|
||||
slot_class: item
|
||||
.slot_class
|
||||
.parse()
|
||||
.unwrap_or_else(|err| panic!("failed to parse slot class: {err}")),
|
||||
sorting_class: item
|
||||
.sorting_class
|
||||
.parse()
|
||||
.unwrap_or_else(|err| panic!("failed to parse sorting class: {err}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ConnectionInfo {
|
||||
pub typ: String,
|
||||
pub role: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DeviceInfo {
|
||||
pub connection_list: Vec<ConnectionInfo>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub device_pins_length: Option<i64>,
|
||||
pub has_activate_state: bool,
|
||||
pub has_atmosphere: bool,
|
||||
pub has_color_state: bool,
|
||||
pub has_lock_state: bool,
|
||||
pub has_mode_state: bool,
|
||||
pub has_on_off_state: bool,
|
||||
pub has_open_state: bool,
|
||||
pub has_reagents: bool,
|
||||
}
|
||||
|
||||
impl From<&stationpedia::Device> for DeviceInfo {
|
||||
fn from(value: &stationpedia::Device) -> Self {
|
||||
DeviceInfo {
|
||||
@@ -525,8 +533,12 @@ impl From<&stationpedia::Device> for DeviceInfo {
|
||||
.connection_list
|
||||
.iter()
|
||||
.map(|(typ, role)| ConnectionInfo {
|
||||
typ: typ.to_string(),
|
||||
role: role.to_string(),
|
||||
typ: typ
|
||||
.parse()
|
||||
.unwrap_or_else(|err| panic!("failed to parse connection type: {err}")),
|
||||
role: role
|
||||
.parse()
|
||||
.unwrap_or_else(|err| panic!("failed to parse connection role: {err}")),
|
||||
})
|
||||
.collect(),
|
||||
device_pins_length: value.devices_length,
|
||||
@@ -542,13 +554,6 @@ impl From<&stationpedia::Device> for DeviceInfo {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct StructureInfo {
|
||||
pub small_grid: bool,
|
||||
}
|
||||
|
||||
impl From<&stationpedia::Structure> for StructureInfo {
|
||||
fn from(value: &stationpedia::Structure) -> Self {
|
||||
StructureInfo {
|
||||
@@ -556,14 +561,6 @@ impl From<&stationpedia::Structure> for StructureInfo {
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Instruction {
|
||||
pub description: String,
|
||||
pub typ: String,
|
||||
pub value: i64,
|
||||
}
|
||||
|
||||
impl From<&stationpedia::Instruction> for Instruction {
|
||||
fn from(value: &stationpedia::Instruction) -> Self {
|
||||
@@ -575,16 +572,6 @@ impl From<&stationpedia::Instruction> for Instruction {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MemoryInfo {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub instructions: Option<BTreeMap<String, Instruction>>,
|
||||
pub memory_access: String,
|
||||
pub memory_size: i64,
|
||||
}
|
||||
|
||||
impl From<&stationpedia::Memory> for MemoryInfo {
|
||||
fn from(value: &stationpedia::Memory) -> Self {
|
||||
MemoryInfo {
|
||||
@@ -594,96 +581,11 @@ impl From<&stationpedia::Memory> for MemoryInfo {
|
||||
.map(|(key, value)| (key.clone(), value.into()))
|
||||
.collect()
|
||||
}),
|
||||
memory_access: value.memory_access.clone(),
|
||||
memory_access: value
|
||||
.memory_access
|
||||
.parse()
|
||||
.unwrap_or_else(|err| panic!("failed to parse memory access: {err}")),
|
||||
memory_size: value.memory_size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct StructureTemplate {
|
||||
pub prefab: PrefabInfo,
|
||||
pub structure: StructureInfo,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct StructureSlotsTemplate {
|
||||
pub prefab: PrefabInfo,
|
||||
pub structure: StructureInfo,
|
||||
pub slots: Vec<SlotInfo>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct StructureLogicTemplate {
|
||||
pub prefab: PrefabInfo,
|
||||
pub structure: StructureInfo,
|
||||
pub logic: LogicInfo,
|
||||
pub slots: Vec<SlotInfo>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct StructureLogicDeviceTemplate {
|
||||
pub prefab: PrefabInfo,
|
||||
pub structure: StructureInfo,
|
||||
pub logic: LogicInfo,
|
||||
pub slots: Vec<SlotInfo>,
|
||||
pub device: DeviceInfo,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct StructureLogicDeviceMemoryTemplate {
|
||||
pub prefab: PrefabInfo,
|
||||
pub structure: StructureInfo,
|
||||
pub logic: LogicInfo,
|
||||
pub slots: Vec<SlotInfo>,
|
||||
pub device: DeviceInfo,
|
||||
pub memory: MemoryInfo,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ItemTemplate {
|
||||
pub prefab: PrefabInfo,
|
||||
pub item: ItemInfo,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ItemSlotsTemplate {
|
||||
pub prefab: PrefabInfo,
|
||||
pub item: ItemInfo,
|
||||
pub slots: Vec<SlotInfo>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ItemLogicTemplate {
|
||||
pub prefab: PrefabInfo,
|
||||
pub item: ItemInfo,
|
||||
pub logic: LogicInfo,
|
||||
pub slots: Vec<SlotInfo>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ItemLogicMemoryTemplate {
|
||||
pub prefab: PrefabInfo,
|
||||
pub item: ItemInfo,
|
||||
pub logic: LogicInfo,
|
||||
pub slots: Vec<SlotInfo>,
|
||||
pub memory: MemoryInfo,
|
||||
}
|
||||
|
||||
@@ -6,17 +6,17 @@ use std::{
|
||||
path::PathBuf,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use proc_macro2::{Ident, Span, TokenStream};
|
||||
use quote::quote;
|
||||
|
||||
pub fn generate_enums(
|
||||
stationpedia: &crate::stationpedia::Stationpedia,
|
||||
enums: &crate::enums::Enums,
|
||||
workspace: &std::path::Path,
|
||||
) -> color_eyre::Result<Vec<PathBuf>> {
|
||||
println!("Writing Enum Listings ...");
|
||||
let enums_path = workspace
|
||||
.join("ic10emu")
|
||||
.join("src")
|
||||
.join("vm")
|
||||
.join("enums");
|
||||
let enums_path = workspace.join("stationeers_data").join("src").join("enums");
|
||||
if !enums_path.exists() {
|
||||
std::fs::create_dir(&enums_path)?;
|
||||
}
|
||||
@@ -81,172 +81,136 @@ pub fn generate_enums(
|
||||
])
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn write_enum_aggragate_mod<T: std::io::Write>(
|
||||
writer: &mut BufWriter<T>,
|
||||
enums: &BTreeMap<String, crate::enums::EnumListing>,
|
||||
) -> color_eyre::Result<()> {
|
||||
let variant_lines = enums
|
||||
.iter()
|
||||
.map(|(name, listing)| {
|
||||
let name = if name.is_empty() || name == "_unnamed" {
|
||||
"Unnamed"
|
||||
} else {
|
||||
name
|
||||
};
|
||||
format!(
|
||||
" {}({}),",
|
||||
name.to_case(Case::Pascal),
|
||||
listing.enum_name.to_case(Case::Pascal)
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
let value_arms = enums
|
||||
.keys()
|
||||
.map(|name| {
|
||||
let variant_name = (if name.is_empty() || name == "_unnamed" {
|
||||
"Unnamed"
|
||||
} else {
|
||||
name
|
||||
})
|
||||
.to_case(Case::Pascal);
|
||||
format!(" Self::{variant_name}(enm) => *enm as u32,",)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
|
||||
let get_str_arms = enums
|
||||
.keys()
|
||||
.map(|name| {
|
||||
let variant_name = (if name.is_empty() || name == "_unnamed" {
|
||||
"Unnamed"
|
||||
} else {
|
||||
name
|
||||
})
|
||||
.to_case(Case::Pascal);
|
||||
format!(" Self::{variant_name}(enm) => enm.get_str(prop),",)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
let iter_chain = enums
|
||||
let (
|
||||
(variant_lines, value_arms),
|
||||
(
|
||||
(get_str_arms, iter_chain),
|
||||
(from_str_arms_iter, display_arms)
|
||||
)
|
||||
): (
|
||||
(Vec<_>, Vec<_>),
|
||||
((Vec<_>, Vec<_>), (Vec<_>, Vec<_>)),
|
||||
) = enums
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, (name, listing))| {
|
||||
let variant_name = (if name.is_empty() || name == "_unnamed" {
|
||||
let variant_name: TokenStream = if name.is_empty() || name == "_unnamed" {
|
||||
"Unnamed"
|
||||
} else {
|
||||
name
|
||||
})
|
||||
.to_case(Case::Pascal);
|
||||
let enum_name = listing.enum_name.to_case(Case::Pascal);
|
||||
if index == 0 {
|
||||
format!("{enum_name}::iter().map(Self::{variant_name})")
|
||||
} else {
|
||||
format!(".chain({enum_name}::iter().map(Self::{variant_name}))")
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
write!(
|
||||
writer,
|
||||
"pub enum BasicEnum {{\n\
|
||||
{variant_lines}
|
||||
}}\n\
|
||||
impl BasicEnum {{\n \
|
||||
pub fn get_value(&self) -> u32 {{\n \
|
||||
match self {{\n \
|
||||
{value_arms}\n \
|
||||
}}\n \
|
||||
}}\n\
|
||||
pub fn get_str(&self, prop: &str) -> Option<&'static str> {{\n \
|
||||
match self {{\n \
|
||||
{get_str_arms}\n \
|
||||
}}\n \
|
||||
}}\n\
|
||||
pub fn iter() -> impl std::iter::Iterator<Item = Self> {{\n \
|
||||
use strum::IntoEnumIterator;\n \
|
||||
{iter_chain}\n \
|
||||
}}
|
||||
}}\n\
|
||||
"
|
||||
)?;
|
||||
let arms = enums
|
||||
.iter()
|
||||
.flat_map(|(name, listing)| {
|
||||
let variant_name = (if name.is_empty() || name == "_unnamed" {
|
||||
"Unnamed"
|
||||
} else {
|
||||
name
|
||||
})
|
||||
.to_case(Case::Pascal);
|
||||
let name = if name == "_unnamed" {
|
||||
"".to_string()
|
||||
} else {
|
||||
name.clone()
|
||||
};
|
||||
let enum_name = listing.enum_name.to_case(Case::Pascal);
|
||||
listing.values.keys().map(move |variant| {
|
||||
let sep = if name.is_empty() { "" } else { "." };
|
||||
let pat = format!("{name}{sep}{variant}").to_lowercase();
|
||||
let variant = variant.to_case(Case::Pascal);
|
||||
format!("\"{pat}\" => Ok(Self::{variant_name}({enum_name}::{variant})),")
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n ");
|
||||
write!(
|
||||
writer,
|
||||
"\
|
||||
impl std::str::FromStr for BasicEnum {{\n \
|
||||
type Err = crate::errors::ParseError;\n \
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {{\n \
|
||||
let end = s.len();\n \
|
||||
match s.to_lowercase().as_str() {{\n \
|
||||
{arms}\n \
|
||||
_ => Err(crate::errors::ParseError{{ line: 0, start: 0, end, msg: format!(\"Unknown enum '{{}}'\", s) }})\n \
|
||||
}}\n \
|
||||
}}\n\
|
||||
}}\
|
||||
"
|
||||
)?;
|
||||
let display_arms = enums
|
||||
.keys()
|
||||
.map(|name| {
|
||||
let variant_name = (if name.is_empty() || name == "_unnamed" {
|
||||
"Unnamed"
|
||||
} else {
|
||||
name
|
||||
})
|
||||
.to_case(Case::Pascal);
|
||||
let name = if name == "_unnamed" {
|
||||
"".to_string()
|
||||
} else {
|
||||
name.clone()
|
||||
};
|
||||
let sep = if name.is_empty() || name == "_unnamed" {
|
||||
.to_case(Case::Pascal)
|
||||
.parse()
|
||||
.unwrap();
|
||||
let fromstr_variant_name = variant_name.clone();
|
||||
let enum_name: TokenStream = listing.enum_name.to_case(Case::Pascal).parse().unwrap();
|
||||
let display_sep = if name.is_empty() || name == "_unnamed" {
|
||||
""
|
||||
} else {
|
||||
"."
|
||||
};
|
||||
let pat = format!("{name}{sep}{{}}");
|
||||
format!(" Self::{variant_name}(enm) => write!(f, \"{pat}\", enm),",)
|
||||
let display_pat = format!("{name}{display_sep}{{}}");
|
||||
let name: TokenStream = if name == "_unnamed" {
|
||||
"".to_string()
|
||||
} else {
|
||||
name.clone()
|
||||
}
|
||||
.parse()
|
||||
.unwrap();
|
||||
(
|
||||
(
|
||||
quote! {
|
||||
#variant_name(#enum_name),
|
||||
},
|
||||
quote! {
|
||||
Self::#variant_name(enm) => *enm as u32,
|
||||
},
|
||||
),
|
||||
(
|
||||
(
|
||||
quote! {
|
||||
Self::#variant_name(enm) => enm.get_str(prop),
|
||||
},
|
||||
if index == 0 {
|
||||
quote! {
|
||||
#enum_name::iter().map(Self::#variant_name)
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
.chain(#enum_name::iter().map(Self::#variant_name))
|
||||
}
|
||||
},
|
||||
),
|
||||
(
|
||||
listing.values.keys().map(move |variant| {
|
||||
let sep = if name.is_empty() { "" } else { "." };
|
||||
let fromstr_pat = format!("{name}{sep}{variant}").to_lowercase();
|
||||
let variant: TokenStream = variant.to_case(Case::Pascal).parse().unwrap();
|
||||
quote! {
|
||||
#fromstr_pat => Ok(Self::#fromstr_variant_name(#enum_name::#variant)),
|
||||
}
|
||||
}),
|
||||
quote! {
|
||||
Self::#variant_name(enm) => write!(f, #display_pat, enm),
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n ");
|
||||
write!(
|
||||
writer,
|
||||
"\
|
||||
impl std::fmt::Display for BasicEnum {{\n \
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {{\n \
|
||||
match self {{\n \
|
||||
{display_arms}\n \
|
||||
}}\n \
|
||||
}}\n\
|
||||
}}\
|
||||
"
|
||||
)?;
|
||||
.unzip();
|
||||
|
||||
let from_str_arms = from_str_arms_iter.into_iter().flatten().collect::<Vec<_>>();
|
||||
|
||||
let tokens = quote! {
|
||||
pub enum BasicEnum {
|
||||
#(#variant_lines)*
|
||||
}
|
||||
|
||||
impl BasicEnum {
|
||||
pub fn get_value(&self) -> u32 {
|
||||
match self {
|
||||
#(#value_arms)*
|
||||
}
|
||||
}
|
||||
pub fn get_str(&self, prop: &str) -> Option<&'static str> {
|
||||
match self {
|
||||
#(#get_str_arms)*
|
||||
}
|
||||
}
|
||||
pub fn iter() -> impl std::iter::Iterator<Item = Self> {
|
||||
use strum::IntoEnumIterator;
|
||||
#(#iter_chain)*
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for BasicEnum {
|
||||
type Err = super::ParseError;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.to_lowercase().as_str() {
|
||||
#(#from_str_arms)*
|
||||
_ => Err(super::ParseError { enm: s.to_string() })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for BasicEnum {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
#(#display_arms)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
write!(writer, "{tokens}",)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_enum_listing<T: std::io::Write>(
|
||||
writer: &mut BufWriter<T>,
|
||||
enm: &crate::enums::EnumListing,
|
||||
@@ -357,10 +321,13 @@ fn write_repr_enum_use_header<T: std::io::Write>(
|
||||
) -> color_eyre::Result<()> {
|
||||
write!(
|
||||
writer,
|
||||
"use serde_derive::{{Deserialize, Serialize}};\n\
|
||||
use strum::{{\n \
|
||||
AsRefStr, Display, EnumIter, EnumProperty, EnumString, FromRepr,\n\
|
||||
}};\n"
|
||||
"{}",
|
||||
quote! {
|
||||
use serde_derive::{{Deserialize, Serialize}};
|
||||
use strum::{
|
||||
AsRefStr, Display, EnumIter, EnumProperty, EnumString, FromRepr,
|
||||
};
|
||||
}
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -369,14 +336,15 @@ fn write_repr_basic_use_header<T: std::io::Write>(
|
||||
writer: &mut BufWriter<T>,
|
||||
script_enums: &[&crate::enums::EnumListing],
|
||||
) -> color_eyre::Result<()> {
|
||||
let enums = script_enums
|
||||
.iter()
|
||||
.map(|enm| Ident::new(&enm.enum_name.to_case(Case::Pascal), Span::call_site()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
write!(
|
||||
writer,
|
||||
"use crate::vm::enums::script_enums::{{ {} }};",
|
||||
script_enums
|
||||
.iter()
|
||||
.map(|enm| enm.enum_name.to_case(Case::Pascal))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
"{}",
|
||||
quote! {use super::script_enums::{ #(#enums),*};},
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -388,60 +356,104 @@ fn write_repr_enum<'a, T: std::io::Write, I, P>(
|
||||
use_phf: bool,
|
||||
) -> color_eyre::Result<()>
|
||||
where
|
||||
P: Display + FromStr + Ord + 'a,
|
||||
P: Display + FromStr + num::integer::Integer + num::cast::AsPrimitive<i64> + 'a,
|
||||
I: IntoIterator<Item = &'a (String, ReprEnumVariant<P>)>,
|
||||
{
|
||||
let additional_strum = if use_phf { "#[strum(use_phf)]\n" } else { "" };
|
||||
let repr = std::any::type_name::<P>();
|
||||
let additional_strum = if use_phf {
|
||||
quote! {#[strum(use_phf)]}
|
||||
} else {
|
||||
TokenStream::new()
|
||||
};
|
||||
let repr = Ident::new(std::any::type_name::<P>(), Span::call_site());
|
||||
let mut sorted: Vec<_> = variants.into_iter().collect::<Vec<_>>();
|
||||
sorted.sort_by_key(|(_, variant)| &variant.value);
|
||||
let default_derive = if sorted
|
||||
let mut derives = [
|
||||
"Debug",
|
||||
"Display",
|
||||
"Clone",
|
||||
"Copy",
|
||||
"PartialEq",
|
||||
"Eq",
|
||||
"PartialOrd",
|
||||
"Ord",
|
||||
"Hash",
|
||||
"EnumString",
|
||||
"AsRefStr",
|
||||
"EnumProperty",
|
||||
"EnumIter",
|
||||
"FromRepr",
|
||||
"Serialize",
|
||||
"Deserialize",
|
||||
]
|
||||
.into_iter()
|
||||
.map(|d| Ident::new(d, Span::call_site()))
|
||||
.collect::<Vec<_>>();
|
||||
if sorted
|
||||
.iter()
|
||||
.any(|(name, _)| name == "None" || name == "Default")
|
||||
{
|
||||
"Default, "
|
||||
} else {
|
||||
""
|
||||
};
|
||||
derives.insert(0, Ident::new("Default", Span::call_site()));
|
||||
}
|
||||
|
||||
let variants = sorted
|
||||
.iter()
|
||||
.map(|(name, variant)| {
|
||||
let variant_name = Ident::new(
|
||||
&name.replace('.', "").to_case(Case::Pascal),
|
||||
Span::call_site(),
|
||||
);
|
||||
let mut props = Vec::new();
|
||||
if variant.deprecated {
|
||||
props.push(quote! {deprecated = "true"});
|
||||
}
|
||||
for (prop_name, prop_val) in &variant.props {
|
||||
let prop_name = Ident::new(prop_name, Span::call_site());
|
||||
let val_string = prop_val.to_string();
|
||||
props.push(quote! { #prop_name = #val_string });
|
||||
}
|
||||
let val: TokenStream = format!("{}{repr}", variant.value).parse().unwrap();
|
||||
let val_string = variant.value.as_().to_string();
|
||||
props.push(quote! {value = #val_string });
|
||||
let default = if variant_name == "None" || variant_name == "Default" {
|
||||
quote! {#[default]}
|
||||
} else {
|
||||
TokenStream::new()
|
||||
};
|
||||
quote! {
|
||||
#[strum(serialize = #name)]
|
||||
#[strum(props(#(#props),*))]
|
||||
#default
|
||||
#variant_name = #val,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let name = Ident::new(name, Span::call_site());
|
||||
|
||||
write!(
|
||||
writer,
|
||||
"#[derive(Debug, {default_derive}Display, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, EnumString, AsRefStr, EnumProperty, EnumIter, FromRepr, Serialize, Deserialize)]\n\
|
||||
{additional_strum}\
|
||||
#[repr({repr})]\n\
|
||||
pub enum {name} {{\n"
|
||||
"{}",
|
||||
quote! {
|
||||
#[derive(#(#derives),*)]
|
||||
#additional_strum
|
||||
#[repr(#repr)]
|
||||
pub enum #name {
|
||||
#(#variants)*
|
||||
}
|
||||
|
||||
impl TryFrom<f64> for #name {
|
||||
type Error = super::ParseError;
|
||||
fn try_from(value: f64) -> Result<Self, <#name as TryFrom<f64>>::Error> {
|
||||
use strum::IntoEnumIterator;
|
||||
if let Some(enm) = #name::iter().find(|enm| (f64::from(*enm as #repr) - value).abs() < f64::EPSILON ) {
|
||||
Ok(enm)
|
||||
} else {
|
||||
Err(super::ParseError {
|
||||
enm: value.to_string()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)?;
|
||||
for (name, variant) in sorted {
|
||||
let variant_name = name.replace('.', "").to_case(Case::Pascal);
|
||||
let serialize = vec![name.clone()];
|
||||
let serialize_str = serialize
|
||||
.into_iter()
|
||||
.map(|s| format!("serialize = \"{s}\""))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
let mut props = Vec::new();
|
||||
if variant.deprecated {
|
||||
props.push("deprecated = \"true\"".to_owned());
|
||||
}
|
||||
for (prop_name, prop_val) in &variant.props {
|
||||
props.push(format!("{prop_name} = r#\"{prop_val}\"#"));
|
||||
}
|
||||
let val = &variant.value;
|
||||
props.push(format!("value = \"{val}\""));
|
||||
let props_str = if !props.is_empty() {
|
||||
format!(", props( {} )", props.join(", "))
|
||||
} else {
|
||||
"".to_owned()
|
||||
};
|
||||
let default = if variant_name == "None" || variant_name == "Default" {
|
||||
"#[default]"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
writeln!(
|
||||
writer,
|
||||
" #[strum({serialize_str}{props_str})]{default} {variant_name} = {val}{repr},"
|
||||
)?;
|
||||
}
|
||||
writeln!(writer, "}}")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use convert_case::{Case, Casing};
|
||||
use proc_macro2::{Ident, Span};
|
||||
use quote::quote;
|
||||
use std::{collections::BTreeMap, path::PathBuf};
|
||||
|
||||
use crate::{generate::utils, stationpedia};
|
||||
@@ -47,82 +49,93 @@ fn write_instructions_enum<T: std::io::Write>(
|
||||
|
||||
write!(
|
||||
writer,
|
||||
"use serde_derive::{{Deserialize, Serialize}};\n\
|
||||
use strum::{{\n \
|
||||
Display, EnumIter, EnumProperty, EnumString, FromRepr,\n\
|
||||
}};\n
|
||||
use crate::vm::object::traits::Programmable;\n\
|
||||
"
|
||||
"{}",
|
||||
quote::quote! {
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use strum::{
|
||||
Display, EnumIter, EnumProperty, EnumString, FromRepr,
|
||||
};
|
||||
use crate::vm::object::traits::Programmable;
|
||||
}
|
||||
)?;
|
||||
|
||||
let inst_variants = instructions
|
||||
.iter()
|
||||
.map(|(name, info)| {
|
||||
let example = &info.example;
|
||||
let desc = &info.desc;
|
||||
let op_count = count_operands(&info.example).to_string();
|
||||
let props =
|
||||
quote::quote! { props( example = #example, desc = #desc, operands = #op_count ) };
|
||||
let name = Ident::new(&name.to_case(Case::Pascal), Span::call_site());
|
||||
quote::quote! {
|
||||
#[strum(#props)] #name,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
write!(
|
||||
writer,
|
||||
"#[derive(Debug, Display, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Serialize, Deserialize)]\n\
|
||||
#[derive(EnumIter, EnumString, EnumProperty, FromRepr)]\n\
|
||||
#[strum(use_phf, serialize_all = \"lowercase\")]\n\
|
||||
#[serde(rename_all = \"lowercase\")]\n\
|
||||
pub enum InstructionOp {{\n\
|
||||
"
|
||||
"{}",
|
||||
quote::quote! {#[derive(Debug, Display, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Serialize, Deserialize)]
|
||||
#[derive(EnumIter, EnumString, EnumProperty, FromRepr)]
|
||||
#[strum(use_phf, serialize_all = "lowercase")]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum InstructionOp {
|
||||
Nop,
|
||||
#(#inst_variants)*
|
||||
|
||||
}
|
||||
}
|
||||
)?;
|
||||
writeln!(writer, " Nop,")?;
|
||||
for (name, info) in &instructions {
|
||||
let props_str = format!(
|
||||
"props( example = \"{}\", desc = \"{}\", operands = \"{}\" )",
|
||||
&info.example,
|
||||
&info.desc,
|
||||
count_operands(&info.example)
|
||||
);
|
||||
writeln!(
|
||||
writer,
|
||||
" #[strum({props_str})] {},",
|
||||
name.to_case(Case::Pascal)
|
||||
)?;
|
||||
}
|
||||
writeln!(writer, "}}")?;
|
||||
|
||||
let exec_arms = instructions
|
||||
.iter()
|
||||
.map(|(name, info)| {
|
||||
let num_operands = count_operands(&info.example);
|
||||
let operands = (0..num_operands)
|
||||
.map(|i| quote! {&operands[#i]})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let trait_name = Ident::new(&name.to_case(Case::Pascal), Span::call_site());
|
||||
let fn_name = Ident::new(&format!("execute_{name}"), Span::call_site());
|
||||
quote! {
|
||||
Self::#trait_name => ic.#fn_name(#(#operands),*),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
write!(
|
||||
writer,
|
||||
"impl InstructionOp {{\n \
|
||||
pub fn num_operands(&self) -> usize {{\n \
|
||||
self.get_str(\"operands\").expect(\"instruction without operand property\").parse::<usize>().expect(\"invalid instruction operand property\")\n \
|
||||
}}\n\
|
||||
\n \
|
||||
pub fn execute<T>(\n \
|
||||
&self,\n \
|
||||
ic: &mut T,\n \
|
||||
operands: &[crate::vm::instructions::operands::Operand],\n \
|
||||
) -> Result<(), crate::errors::ICError>\n \
|
||||
where\n \
|
||||
T: Programmable,\n\
|
||||
{{\n \
|
||||
let num_operands = self.num_operands();\n \
|
||||
if operands.len() != num_operands {{\n \
|
||||
return Err(crate::errors::ICError::mismatch_operands(operands.len(), num_operands as u32));\n \
|
||||
}}\n \
|
||||
match self {{\n \
|
||||
Self::Nop => Ok(()),\n \
|
||||
"
|
||||
)?;
|
||||
"{}",
|
||||
quote! {
|
||||
impl InstructionOp {
|
||||
pub fn num_operands(&self) -> usize {
|
||||
self.get_str("operands")
|
||||
.expect("instruction without operand property")
|
||||
.parse::<usize>()
|
||||
.expect("invalid instruction operand property")
|
||||
}
|
||||
|
||||
for (name, info) in instructions {
|
||||
let num_operands = count_operands(&info.example);
|
||||
let operands = (0..num_operands)
|
||||
.map(|i| format!("&operands[{}]", i))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
let trait_name = name.to_case(Case::Pascal);
|
||||
writeln!(
|
||||
writer,
|
||||
" Self::{trait_name} => ic.execute_{name}({operands}),",
|
||||
)?;
|
||||
}
|
||||
|
||||
write!(
|
||||
writer,
|
||||
" }}\
|
||||
}}\n\
|
||||
}}
|
||||
"
|
||||
pub fn execute<T>(
|
||||
&self,
|
||||
ic: &mut T,
|
||||
operands: &[crate::vm::instructions::operands::Operand],
|
||||
) -> Result<(), crate::errors::ICError>
|
||||
where
|
||||
T: Programmable,
|
||||
{
|
||||
let num_operands = self.num_operands();
|
||||
if operands.len() != num_operands {
|
||||
return Err(crate::errors::ICError::mismatch_operands(operands.len(), num_operands as u32));
|
||||
}
|
||||
match self {
|
||||
Self::Nop => Ok(()),
|
||||
#(#exec_arms)*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
@@ -134,7 +147,8 @@ fn write_instruction_trait<T: std::io::Write>(
|
||||
) -> color_eyre::Result<()> {
|
||||
let (name, info) = instruction;
|
||||
let op_name = name.to_case(Case::Pascal);
|
||||
let trait_name = format!("{op_name}Instruction");
|
||||
let trait_name = Ident::new(&format!("{op_name}Instruction"), Span::call_site());
|
||||
let op_ident = Ident::new(&op_name, Span::call_site());
|
||||
let operands = operand_names(&info.example)
|
||||
.iter()
|
||||
.map(|name| {
|
||||
@@ -142,13 +156,13 @@ fn write_instruction_trait<T: std::io::Write>(
|
||||
if n == "str" {
|
||||
n = "string";
|
||||
}
|
||||
format!(
|
||||
"{}: &crate::vm::instructions::operands::Operand",
|
||||
n.to_case(Case::Snake)
|
||||
)
|
||||
let n = Ident::new(&n.to_case(Case::Snake), Span::call_site());
|
||||
quote! {
|
||||
#n: &crate::vm::instructions::operands::Operand
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let operands_inner = operand_names(&info.example)
|
||||
.iter()
|
||||
.map(|name| {
|
||||
@@ -156,13 +170,13 @@ fn write_instruction_trait<T: std::io::Write>(
|
||||
if n == "str" {
|
||||
n = "string";
|
||||
}
|
||||
format!(
|
||||
"{}: &crate::vm::instructions::operands::InstOperand",
|
||||
n.to_case(Case::Snake)
|
||||
)
|
||||
let n = Ident::new(&n.to_case(Case::Snake), Span::call_site());
|
||||
quote! {
|
||||
#n: &crate::vm::instructions::operands::InstOperand
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let operand_call = operand_names(&info.example)
|
||||
.iter()
|
||||
.enumerate()
|
||||
@@ -171,24 +185,27 @@ fn write_instruction_trait<T: std::io::Write>(
|
||||
if n == "str" {
|
||||
n = "string";
|
||||
}
|
||||
format!(
|
||||
"&crate::vm::instructions::operands::InstOperand::new({}, InstructionOp::{op_name}, {index})",
|
||||
n.to_case(Case::Snake)
|
||||
)
|
||||
let n = Ident::new(&n.to_case(Case::Snake), Span::call_site());
|
||||
quote!{
|
||||
&crate::vm::instructions::operands::InstOperand::new(#n, InstructionOp::#op_ident, #index)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
.collect::<Vec<_>>();
|
||||
let example = utils::strip_color(&info.example);
|
||||
let fn_name = Ident::new(&format!("execute_{name}"), Span::call_site());
|
||||
write!(
|
||||
writer,
|
||||
"pub trait {trait_name}: IntegratedCircuit {{\n \
|
||||
/// {example} \n \
|
||||
fn execute_{name}(&mut self, {operands}) -> Result<(), crate::errors::ICError> {{\n \
|
||||
{trait_name}::execute_inner(self, {operand_call})\n \
|
||||
}}\n \
|
||||
/// {example} \n \
|
||||
fn execute_inner(&mut self, {operands_inner}) -> Result<(), crate::errors::ICError>;\n\
|
||||
}}"
|
||||
"{}",
|
||||
quote! {
|
||||
pub trait #trait_name: IntegratedCircuit {
|
||||
#[doc = #example]
|
||||
fn #fn_name(&mut self, #(#operands),*) -> Result<(), crate::errors::ICError> {
|
||||
#trait_name::execute_inner(self, #(#operand_call),*)
|
||||
}
|
||||
#[doc = #example]
|
||||
fn execute_inner(&mut self, #(#operands_inner),*) -> Result<(), crate::errors::ICError>;
|
||||
}
|
||||
}
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -208,10 +225,11 @@ fn operand_names(example: &str) -> Vec<String> {
|
||||
fn write_instruction_trait_use<T: std::io::Write>(writer: &mut T) -> color_eyre::Result<()> {
|
||||
write!(
|
||||
writer,
|
||||
"\
|
||||
use crate::vm::object::traits::IntegratedCircuit;\n\
|
||||
use crate::vm::instructions::enums::InstructionOp;\n\
|
||||
"
|
||||
"{}",
|
||||
quote! {
|
||||
use crate::vm::object::traits::IntegratedCircuit;
|
||||
use crate::vm::instructions::enums::InstructionOp;
|
||||
}
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -222,15 +240,20 @@ fn write_instruction_super_trait<T: std::io::Write>(
|
||||
) -> color_eyre::Result<()> {
|
||||
let traits = instructions
|
||||
.keys()
|
||||
.map(|name| format!("{}Instruction", name.to_case(Case::Pascal)))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" + ");
|
||||
.map(|name| {
|
||||
Ident::new(
|
||||
&format!("{}Instruction", name.to_case(Case::Pascal)),
|
||||
Span::call_site(),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
write!(
|
||||
writer,
|
||||
"\
|
||||
pub trait ICInstructable: {traits} {{}}\n\
|
||||
impl <T> ICInstructable for T where T: {traits} {{}}
|
||||
"
|
||||
"{}",
|
||||
quote! {
|
||||
pub trait ICInstructable: #(#traits +)* {}
|
||||
impl <T> ICInstructable for T where T: #(#traits )+* {}
|
||||
}
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -24,6 +24,17 @@ struct Args {
|
||||
|
||||
const PACKAGES: &[&str] = &["ic10lsp_wasm", "ic10emu_wasm"];
|
||||
const VALID_VERSION_TYPE: &[&str] = &["patch", "minor", "major"];
|
||||
const VALID_GENERATE_TYPE: &[&str] = &["enums", "instructions", "database"];
|
||||
const DEFAULT_GENERATE: &[&str] = &["enums"];
|
||||
fn parse_generate_modules(s: &str) -> Result<String, String> {
|
||||
if !VALID_GENERATE_TYPE.contains(&s) {
|
||||
let valid_str = VALID_GENERATE_TYPE.join(", ");
|
||||
return Err(format!(
|
||||
"{s} is not a valid generate module. One of: {valid_str}"
|
||||
));
|
||||
}
|
||||
Ok(s.to_string())
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
enum Task {
|
||||
@@ -54,6 +65,8 @@ enum Task {
|
||||
/// update changelog
|
||||
Changelog {},
|
||||
Generate {
|
||||
#[arg(long, short = 'm', value_delimiter = ',', default_values = DEFAULT_GENERATE, value_parser = parse_generate_modules)]
|
||||
modules: Vec<String>,
|
||||
#[arg()]
|
||||
/// Path to Stationeers installation. Used to locate "Stationpedia.json" and "Enums.json"
|
||||
/// generated by https://github.com/Ryex/StationeersStationpediaExtractor
|
||||
@@ -166,7 +179,7 @@ fn main() -> color_eyre::Result<()> {
|
||||
.status()
|
||||
.map_err(|e| Error::Command(format!("{}", cmd.get_program().to_string_lossy()), e))?;
|
||||
}
|
||||
Task::Generate { path } => {
|
||||
Task::Generate { modules, path } => {
|
||||
let path = match path {
|
||||
Some(path) => {
|
||||
let mut path = std::path::PathBuf::from(path);
|
||||
@@ -214,7 +227,11 @@ fn main() -> color_eyre::Result<()> {
|
||||
&& path.join("Stationpedia.json").exists()
|
||||
&& path.join("Enums.json").exists()
|
||||
{
|
||||
generate::generate(&path, &workspace)?;
|
||||
generate::generate(
|
||||
&path,
|
||||
&workspace,
|
||||
&modules.iter().map(String::as_str).collect::<Vec<_>>(),
|
||||
)?;
|
||||
} else {
|
||||
return Err(Error::BadStationeersPath(path).into());
|
||||
}
|
||||
|
||||
@@ -214,7 +214,7 @@ pub struct Memory {
|
||||
#[serde(rename = "MemoryAccess")]
|
||||
pub memory_access: String,
|
||||
#[serde(rename = "MemorySize")]
|
||||
pub memory_size: i64,
|
||||
pub memory_size: u32,
|
||||
#[serde(rename = "MemorySizeReadable")]
|
||||
pub memory_size_readable: String,
|
||||
}
|
||||
@@ -308,7 +308,7 @@ pub struct Device {
|
||||
#[serde(rename = "ConnectionList")]
|
||||
pub connection_list: Vec<(String, String)>,
|
||||
#[serde(rename = "DevicesLength")]
|
||||
pub devices_length: Option<i64>,
|
||||
pub devices_length: Option<u32>,
|
||||
#[serde(rename = "HasActivateState")]
|
||||
pub has_activate_state: bool,
|
||||
#[serde(rename = "HasAtmosphere")]
|
||||
|
||||
Reference in New Issue
Block a user