refactor(vm): pull generated data into new crate

pregenerate prefab templates -> locked behind crate feature
This commit is contained in:
Rachel Powers
2024-05-19 21:44:15 -07:00
parent b0bdc37a8a
commit 4346bc5302
40 changed files with 59148 additions and 15792 deletions

View File

@@ -4,6 +4,7 @@ version.workspace = true
edition.workspace = true
[dependencies]
stationeers_data = { path = "../stationeers_data" }
clap = { version = "4.5.4", features = ["derive", "env"] }
color-eyre = "0.6.3"
convert_case = "0.6.0"
@@ -11,15 +12,21 @@ indexmap = { version = "2.2.6", features = ["serde"] }
# onig = "6.4.0"
phf_codegen = "0.11.2"
regex = "1.10.4"
serde = "1.0.200"
serde_derive = "1.0.200"
serde = "1.0.202"
serde_derive = "1.0.202"
serde_ignored = "0.1.10"
serde_json = "1.0.116"
serde_json = "1.0.117"
serde_path_to_error = "0.1.16"
serde_with = "3.8.1"
textwrap = { version = "0.16.1", default-features = false }
thiserror = "1.0.58"
thiserror = "1.0.61"
onig = { git = "https://github.com/rust-onig/rust-onig", revision = "fa90c0e97e90a056af89f183b23cd417b59ee6a2" }
tracing = "0.1.40"
quote = "1.0.36"
prettyplease = "0.2.20"
syn = "2.0.64"
proc-macro2 = "1.0.82"
num-integer = "0.1.46"
num = "0.4.3"
uneval = "0.2.4"

View File

@@ -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)?;

View File

@@ -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,
}

View File

@@ -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(())
}

View File

@@ -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(())
}

View File

@@ -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());
}

View File

@@ -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")]