diff --git a/Cargo.toml b/Cargo.toml index 1644998..e542faf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ utf16string = "0.2" [features] default = ["artemis", "artemis-arc", "bgi", "bgi-arc", "bgi-img", "cat-system", "cat-system-arc", "cat-system-img", "circus", "escude", "escude-arc", "kirikiri", "kirikiri-img", "will-plus", "yaneurao", "yaneurao-itufuru"] -artemis = [] +artemis = ["utils-escape"] artemis-arc = ["artemis", "msg_tool_macro/artemis-arc", "sha1"] bgi = [] bgi-arc = ["bgi", "rand", "utils-bit-stream"] diff --git a/src/args.rs b/src/args.rs index 0e278ad..3036ece 100644 --- a/src/args.rs +++ b/src/args.rs @@ -3,7 +3,7 @@ use clap::{ArgAction, ArgGroup, Parser, Subcommand}; /// Tools for export and import scripts #[derive(Parser, Debug)] -#[clap(group = ArgGroup::new("encodingg").multiple(false), group = ArgGroup::new("output_encodingg").multiple(false), group = ArgGroup::new("archive_encodingg").multiple(false))] +#[clap(group = ArgGroup::new("encodingg").multiple(false), group = ArgGroup::new("output_encodingg").multiple(false), group = ArgGroup::new("archive_encodingg").multiple(false), group = ArgGroup::new("artemis_indentg").multiple(false))] #[command( version, about, @@ -161,6 +161,19 @@ pub struct Arg { #[arg(long, global = true)] /// Disable Artemis archive (.pfs) XOR encryption when packing. pub artemis_arc_disable_xor: bool, + #[cfg(feature = "artemis")] + #[arg(long, global = true, group = "artemis_indentg")] + /// Artemis script indent size, used to format Artemis script. + /// Default is 4 spaces. + pub artemis_indent: Option, + #[cfg(feature = "artemis")] + #[arg(long, global = true, action = ArgAction::SetTrue, group = "artemis_indentg")] + /// Disable Artemis script indent, used to format Artemis script. + pub artemis_no_indent: bool, + #[cfg(feature = "artemis")] + #[arg(long, global = true, default_value_t = 100)] + /// Max line width in Artemis script, used to format Artemis script. + pub artemis_max_line_width: usize, #[command(subcommand)] /// Command pub command: Command, diff --git a/src/main.rs b/src/main.rs index a331e24..dd8473e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1434,6 +1434,12 @@ fn main() { kirikiri_pimg_overlay: arg.kirikiri_pimg_overlay, #[cfg(feature = "artemis-arc")] artemis_arc_disable_xor: arg.artemis_arc_disable_xor, + #[cfg(feature = "artemis")] + artemis_indent: arg.artemis_indent, + #[cfg(feature = "artemis")] + artemis_no_indent: arg.artemis_no_indent, + #[cfg(feature = "artemis")] + artemis_max_line_width: arg.artemis_max_line_width, }; match &arg.command { args::Command::Export { input, output } => { diff --git a/src/scripts/artemis/ast/dump.rs b/src/scripts/artemis/ast/dump.rs new file mode 100644 index 0000000..83e4f00 --- /dev/null +++ b/src/scripts/artemis/ast/dump.rs @@ -0,0 +1,244 @@ +use super::types::*; +use std::io::Write; + +struct LenChecker { + target_len: usize, + current_len: usize, +} + +impl LenChecker { + fn new(target_len: usize) -> Self { + LenChecker { + target_len, + current_len: 0, + } + } + + fn check(&mut self, value: &Value) -> bool { + match value { + Value::Float(f) => { + if f.fract() == 0.0 { + self.current_len += format!("{:.1}", f).len(); + } else { + self.current_len += format!("{}", f).len(); + } + } + Value::Int(i) => self.current_len += format!("{}", i).len(), + Value::Str(s) => self.current_len += s.len() + 2, + Value::KeyVal((k, v)) => { + self.current_len += k.as_bytes().len() + 3; + if !self.check(v) { + return false; + } + } + Value::Array(arr) => { + self.current_len += 1; + for v in arr { + if !self.check(v) { + return false; + } + self.current_len += 2; + } + self.current_len += 1; + } + Value::Null => { + self.current_len += 3; // "nil" + } + } + if self.current_len > self.target_len { + return false; + } + true + } +} + +pub struct Dumper<'a> { + current_indent: usize, + writer: Box, + indent: Option, + max_line_width: usize, + current_line_width: usize, +} + +impl<'a> Dumper<'a> { + pub fn new(writer: W) -> Self { + Dumper { + current_indent: 0, + writer: Box::new(writer), + indent: Some(4), + max_line_width: 100, + current_line_width: 0, + } + } + + pub fn set_indent(&mut self, indent: usize) { + self.indent = Some(indent); + } + + pub fn set_no_indent(&mut self) { + self.indent = None; + } + + pub fn set_max_line_width(&mut self, max_line_width: usize) { + self.max_line_width = max_line_width; + } + + fn dump_f64(&mut self, f: &f64) -> std::io::Result<()> { + if f.fract() == 0.0 { + write!(self.writer, "{:.1}", f) + } else { + write!(self.writer, "{}", f) + } + } + + pub fn dump(mut self, ast: &AstFile) -> std::io::Result<()> { + if self.indent.is_none() { + self.writer.write(b"astver=")?; + self.dump_f64(&ast.astver)?; + if let Some(astname) = &ast.astname { + self.writer.write(b"\nastname = \"")?; + self.writer.write(astname.as_bytes())?; + }; + self.writer.write(b"\"\nast=")?; + self.dump_value(&ast.ast)?; + } else { + self.writer.write(b"astver = ")?; + self.dump_f64(&ast.astver)?; + if let Some(astname) = &ast.astname { + self.writer.write(b"\nastname = \"")?; + self.writer.write(astname.as_bytes())?; + }; + self.writer.write(b"\"\nast = ")?; + self.current_line_width = 6; + self.dump_value(&ast.ast)?; + } + self.writer.write(b"\n")?; + Ok(()) + } + + fn dump_value(&mut self, v: &Value) -> std::io::Result<()> { + if self.indent.is_none() { + match v { + Value::Float(f) => self.dump_f64(f)?, + Value::Int(i) => write!(self.writer, "{}", i)?, + Value::Str(s) => { + self.writer.write(b"\"")?; + self.writer.write(s.as_bytes())?; + self.writer.write(b"\"")?; + } + Value::KeyVal((k, v)) => { + self.writer.write(k.as_bytes())?; + self.writer.write(b"=")?; + self.dump_value(v)?; + } + Value::Array(arr) => { + self.writer.write(b"{")?; + for (i, v) in arr.iter().enumerate() { + if i > 0 { + self.writer.write(b",")?; + } + self.dump_value(v)?; + } + self.writer.write(b"}")?; + } + Value::Null => { + self.writer.write(b"nil")?; + } + } + } else { + match v { + Value::Float(f) => self.dump_f64(f)?, + Value::Int(i) => write!(self.writer, "{}", i)?, + Value::Str(s) => { + self.writer.write(b"\"")?; + self.writer.write(s.as_bytes())?; + self.writer.write(b"\"")?; + } + Value::KeyVal((k, v)) => { + let bytes = k.as_bytes(); + self.writer.write(bytes)?; + self.writer.write(b" = ")?; + self.current_line_width += bytes.len() + 3; + if v.is_array() { + let tlen = self.current_line_width + self.current_indent; + if tlen < self.max_line_width { + let mut checker = LenChecker::new(self.max_line_width - tlen); + if checker.check(v) { + self.dump_value_in_one(v)?; + return Ok(()); + } + } + } + self.dump_value(v)?; + } + Value::Array(a) => { + let tlen = self.current_line_width + self.current_indent; + if tlen < self.max_line_width { + let mut checker = LenChecker::new(self.max_line_width - tlen); + if checker.check(v) { + self.dump_value_in_one(v)?; + return Ok(()); + } + } + self.writer.write(b"{\n")?; + self.current_indent += self.indent.unwrap(); + for (i, v) in a.iter().enumerate() { + if i > 0 { + self.writer.write(b",\n")?; + } + self.dump_indent()?; + self.current_line_width = 0; + self.dump_value(v)?; + } + self.current_indent -= self.indent.unwrap(); + self.writer.write(b",\n")?; + self.dump_indent()?; + self.writer.write(b"}")?; + } + Value::Null => { + self.writer.write(b"nil")?; + } + } + } + Ok(()) + } + + fn dump_indent(&mut self) -> std::io::Result<()> { + for _ in 0..self.current_indent { + self.writer.write(b" ")?; + } + Ok(()) + } + + fn dump_value_in_one(&mut self, v: &Value) -> std::io::Result<()> { + match v { + Value::Float(f) => self.dump_f64(f)?, + Value::Int(i) => write!(self.writer, "{}", i)?, + Value::Str(s) => { + self.writer.write(b"\"")?; + self.writer.write(s.as_bytes())?; + self.writer.write(b"\"")?; + } + Value::KeyVal((k, v)) => { + let bytes = k.as_bytes(); + self.writer.write(bytes)?; + self.writer.write(b"=")?; + self.dump_value_in_one(v)?; + } + Value::Array(arr) => { + self.writer.write(b"{")?; + for (i, v) in arr.iter().enumerate() { + if i > 0 { + self.writer.write(b", ")?; + } + self.dump_value_in_one(v)?; + } + self.writer.write(b"}")?; + } + Value::Null => { + self.writer.write(b"nil")?; + } + } + Ok(()) + } +} diff --git a/src/scripts/artemis/ast/mod.rs b/src/scripts/artemis/ast/mod.rs new file mode 100644 index 0000000..c2daa96 --- /dev/null +++ b/src/scripts/artemis/ast/mod.rs @@ -0,0 +1,156 @@ +mod dump; +mod parser; +mod text; +mod types; + +use crate::scripts::base::*; +use crate::types::*; +use crate::utils::encoding::*; +use anyhow::Result; +use std::io::Write; +use types::*; + +#[derive(Debug)] +pub struct AstScriptBuilder {} + +impl AstScriptBuilder { + pub fn new() -> Self { + AstScriptBuilder {} + } +} + +impl ScriptBuilder for AstScriptBuilder { + fn default_encoding(&self) -> Encoding { + Encoding::Utf8 + } + + fn build_script( + &self, + buf: Vec, + _filename: &str, + encoding: Encoding, + _archive_encoding: Encoding, + config: &ExtraConfig, + ) -> Result> { + Ok(Box::new(AstScript::new(buf, encoding, config)?)) + } + + fn extensions(&self) -> &'static [&'static str] { + &["ast"] + } + + fn script_type(&self) -> &'static ScriptType { + &ScriptType::Artemis + } +} + +#[derive(Debug)] +pub struct AstScript { + ast: AstFile, + indent: Option, + max_line_width: usize, + no_indent: bool, +} + +impl AstScript { + pub fn new(buf: Vec, encoding: Encoding, config: &ExtraConfig) -> Result { + let parser = parser::Parser::new(&buf, encoding); + let ast = parser.parse()?; + Ok(AstScript { + ast, + indent: config.artemis_indent, + max_line_width: config.artemis_max_line_width, + no_indent: config.artemis_no_indent, + }) + } +} + +impl Script for AstScript { + fn default_output_script_type(&self) -> OutputScriptType { + OutputScriptType::Json + } + + fn default_format_type(&self) -> FormatOptions { + FormatOptions::None + } + + fn extract_messages(&self) -> Result> { + let mut messages = Vec::new(); + let ast = &self.ast.ast; + let mut block_name = ast["label"]["top"]["block"] + .as_str() + .ok_or(anyhow::anyhow!("Missing top block name"))?; + let mut block = &ast[block_name]; + let mut lang = None; + loop { + if let Some(save_title) = block[Key("savetitle")]["text"].as_str() { + messages.push(Message { + name: None, + message: save_title.to_string(), + }); + } + let text = &block["text"]; + if text.is_array() { + let lan = match lang { + Some(l) => l, + None => { + for l in text.kv_keys() { + if l != "vo" { + lang = Some(l); + } + } + match lang { + Some(l) => l, + // No text found, continue to next block + None => continue, + } + } + }; + let tex = &text[lan]; + for item in tex.members() { + let name = item["name"].last_member().as_string(); + let message = text::TextGenerator::new().generate(item)?; + messages.push(Message { + name: name, + message: message + .replace("", "\n") + .replace("", "\n") + .trim_end_matches("\n") + .to_string(), + }); + } + } + // #TODO: SELECTS + block_name = match block["linknext"].as_str() { + Some(name) => name, + None => break, + }; + block = &ast[block_name]; + } + Ok(messages) + } + + fn import_messages<'a>( + &'a self, + _messages: Vec, + mut file: Box, + encoding: Encoding, + _replacement: Option<&'a ReplacementTable>, + ) -> Result<()> { + let ast = self.ast.clone(); + let mut writer = Vec::new(); + let mut dumper = dump::Dumper::new(&mut writer); + if self.no_indent { + dumper.set_no_indent(); + } else if let Some(indent) = self.indent { + dumper.set_indent(indent); + } + dumper.set_max_line_width(self.max_line_width); + dumper.dump(&ast)?; + let data = String::from_utf8(writer)?; + let encoded = encode_string(encoding, &data, false)?; + file.write_all(&encoded)?; + file.flush()?; + Ok(()) + } +} diff --git a/src/scripts/artemis/ast/parser.rs b/src/scripts/artemis/ast/parser.rs new file mode 100644 index 0000000..a87ed3c --- /dev/null +++ b/src/scripts/artemis/ast/parser.rs @@ -0,0 +1,284 @@ +use super::types::*; +use crate::types::*; +use crate::utils::encoding::*; +use anyhow::Result; + +pub struct Parser<'a> { + str: &'a [u8], + pos: usize, + len: usize, + line: usize, + line_index: usize, + encoding: Encoding, +} + +impl<'a> Parser<'a> { + pub fn new>(str: &'a S, encoding: Encoding) -> Self { + let str = str.as_ref(); + Parser { + str, + pos: 0, + len: str.len(), + line: 1, + line_index: 1, + encoding, + } + } + + pub fn parse(mut self) -> Result { + self.erase_whitespace(); + self.parse_indent(b"astver")?; + self.parse_equal()?; + let astver = self.parse_f64()?; + self.erase_whitespace(); + let mut astname = None; + if self.is_indent(b"astname") { + self.parse_indent(b"astname")?; + self.parse_equal()?; + astname = Some(self.parse_str()?.to_string()); + self.erase_whitespace(); + } + self.parse_indent(b"ast")?; + self.parse_equal()?; + let ast = self.parse_value()?; + Ok(AstFile { + astver, + astname, + ast, + }) + } + + fn parse_equal(&mut self) -> Result<()> { + self.erase_whitespace(); + match self.next() { + Some(b'=') => Ok(()), + _ => self.error("expected '='"), + } + } + + fn parse_value(&mut self) -> Result { + self.erase_whitespace(); + match self.peek() { + Some(t) => match t { + b'"' => return self.parse_str().map(|x| Value::Str(x.to_string())), + b'-' | b'.' | b'0'..=b'9' => return self.parse_any_number(), + b'n' => { + if self.is_indent(b"nil") { + self.pos += 3; // Skip "nil" + Ok(Value::Null) + } else { + self.parse_key_val() + } + } + b'_' | b'a'..=b'z' | b'A'..=b'Z' | b'[' | b']' => return self.parse_key_val(), + b'{' => return self.parse_array(), + _ => return self.error(format!("unexpected token: {}", t)), + }, + None => return self.error("unexpected eof"), + } + } + + fn parse_array(&mut self) -> Result { + self.erase_whitespace(); + self.parse_indent(b"{")?; + let mut array = Vec::new(); + loop { + self.erase_whitespace(); + match self.peek() { + Some(b'}') => { + self.eat_char(); + break; + } + Some(_) => { + let val = self.parse_value()?; + array.push(val); + match self.peek() { + Some(b',') => { + self.eat_char(); + } + _ => {} + } + } + None => return self.error("unexpected eof"), + } + } + Ok(Value::Array(array)) + } + + fn parse_any_number(&mut self) -> Result { + self.erase_whitespace(); + let start = self.pos; + while let Some(c) = self.peek() { + if c == b'.' || c == b'-' || c.is_ascii_digit() { + self.eat_char(); + } else { + break; + } + } + let s = std::str::from_utf8(&self.str[start..self.pos])?; + if s.contains('.') { + s.parse() + .map(Value::Float) + .map_err(|e| self.error2(format!("failed to parse f64: {}", e))) + } else { + s.parse() + .map(Value::Int) + .map_err(|e| self.error2(format!("failed to parse i64: {}", e))) + } + } + + fn parse_f64(&mut self) -> Result { + self.erase_whitespace(); + let start = self.pos; + while let Some(c) = self.peek() { + if c == b'.' || c == b'-' || c.is_ascii_digit() { + self.eat_char(); + } else { + break; + } + } + let s = std::str::from_utf8(&self.str[start..self.pos])?; + s.parse() + .map_err(|e| self.error2(format!("failed to parse f64: {}", e))) + } + + fn parse_str(&mut self) -> Result { + self.erase_whitespace(); + self.parse_indent(b"\"")?; + let start = self.pos; + let end = loop { + match self.next() { + Some(c) => { + if c == b'"' { + break self.pos - 1; + } + } + None => return self.error("unexpected eof"), + } + }; + decode_to_string(self.encoding, &self.str[start..end], true).map_err(|e| self.error2(e)) + } + + fn erase_whitespace(&mut self) { + while let Some(c) = self.peek() { + if c == b' ' || c == b'\t' || c == b'\n' || c == b'\r' { + if c == b'\n' { + self.line += 1; + self.line_index = 1; + } else { + self.line_index += 1; + } + self.eat_char(); + } else { + break; + } + } + } + + fn next(&mut self) -> Option { + if self.pos < self.len { + let c = self.str[self.pos]; + self.pos += 1; + if c == b'\n' { + self.line += 1; + self.line_index = 1; + } else { + self.line_index += 1; + } + Some(c) + } else { + None + } + } + + fn peek(&self) -> Option { + if self.pos < self.len { + Some(self.str[self.pos]) + } else { + None + } + } + + fn parse_key_val(&mut self) -> Result { + let key = self.get_indent()?; + self.parse_equal()?; + let val = self.parse_value()?; + Ok(Value::KeyVal((key.to_string(), Box::new(val)))) + } + + fn get_indent(&mut self) -> Result { + self.erase_whitespace(); + let start = self.pos; + let mut is_first = true; + let end = loop { + match self.peek() { + Some(t) => match t { + b'_' | b'a'..=b'z' | b'A'..=b'Z' | b'[' | b']' => self.eat_char(), + b'0'..=b'9' => { + if is_first { + return self.error("unexpected digit"); + } + self.eat_char(); + } + b' ' | b'\t' | b'=' | b'\n' | b'\r' => break self.pos, + _ => return self.error("unexpected token"), + }, + None => return self.error("unexpected eof"), + } + is_first = false; + }; + decode_to_string(self.encoding, &self.str[start..end], true).map_err(|e| self.error2(e)) + } + + fn is_indent(&self, indent: &[u8]) -> bool { + if self.pos + indent.len() > self.len { + return false; + } + for (i, c) in indent.iter().enumerate() { + if self.str[self.pos + i] != *c { + return false; + } + } + true + } + + fn parse_indent(&mut self, indent: &[u8]) -> Result<()> { + for c in indent { + match self.next() { + Some(x) => { + if x != *c { + return self.error("unexpected indent"); + } + } + None => return self.error("unexpected eof"), + } + } + Ok(()) + } + + fn eat_char(&mut self) { + if self.pos < self.len { + self.pos += 1; + } + } + + fn error2(&self, msg: T) -> anyhow::Error + where + T: std::fmt::Display, + { + anyhow::Error::msg(format!( + "Failed to parse at position line {} column {} (byte {}): {}", + self.line, self.line_index, self.pos, msg + )) + } + + fn error(&self, msg: T) -> Result + where + T: std::fmt::Display, + { + Err(anyhow::Error::msg(format!( + "Failed to parse at position line {} column {} (byte {}): {}", + self.line, self.line_index, self.pos, msg + ))) + } +} diff --git a/src/scripts/artemis/ast/text.rs b/src/scripts/artemis/ast/text.rs new file mode 100644 index 0000000..106216c --- /dev/null +++ b/src/scripts/artemis/ast/text.rs @@ -0,0 +1,118 @@ +use super::types::*; +use crate::utils::escape::*; +use anyhow::Result; + +pub struct TextGenerator { + data: String, +} + +impl TextGenerator { + pub fn new() -> Self { + TextGenerator { + data: String::new(), + } + } + + pub fn generate(mut self, v: &Value) -> Result { + for (i, item) in v.members().enumerate() { + match item { + Value::Str(s) => { + self.data.push_str(s); + } + Value::Float(_) => { + return Err(anyhow::anyhow!( + "Unexpected float value at {} in text: item={:?}, {:?}", + i, + item, + v + )); + } + Value::Int(_) => { + return Err(anyhow::anyhow!( + "Unexpected int value at {} in text: item={:?}, {:?}", + i, + item, + v + )); + } + Value::KeyVal((k, _)) => { + if k != "name" { + return Err(anyhow::anyhow!( + "Unexpected key at {} in text: item={:?}, {:?}", + i, + item, + v + )); + } + } + Value::Array(arr) => { + self.data.push('<'); + let mut first = true; + for item in arr { + if !first { + self.data.push(' '); + } + first = false; + match item { + Value::Str(s) => { + self.data.push_str(s); + } + Value::Float(f) => { + if f.fract() == 0.0 { + self.data.push_str(&format!("{:.1}", f)); + } else { + self.data.push_str(&f.to_string()); + } + } + Value::Int(i) => { + self.data.push_str(&i.to_string()); + } + Value::KeyVal((k, v)) => { + self.data.push_str(k); + self.data.push('='); + match v.as_ref() { + Value::Str(s) => { + self.data.push('"'); + self.data.push_str(&escape_xml_attr_value(s)); + self.data.push('"'); + } + Value::Float(f) => { + if f.fract() == 0.0 { + self.data.push_str(&format!("{:.1}", f)); + } else { + self.data.push_str(&f.to_string()); + } + } + Value::Int(i) => { + self.data.push_str(&i.to_string()); + } + Value::Null => {} + _ => { + return Err(anyhow::anyhow!( + "Unexpected value type in text: item={:?}, {:?}", + item, + arr + )); + } + } + } + Value::Array(_) => { + return Err(anyhow::anyhow!( + "Unexpected nested array in text: item={:?}, {:?}", + item, + arr + )); + } + _ => { + first = true; + } + } + } + self.data.push('>'); + } + _ => {} + } + } + Ok(self.data) + } +} diff --git a/src/scripts/artemis/ast/types.rs b/src/scripts/artemis/ast/types.rs new file mode 100644 index 0000000..ae66a5e --- /dev/null +++ b/src/scripts/artemis/ast/types.rs @@ -0,0 +1,387 @@ +use std::cmp::{PartialEq, PartialOrd}; +use std::ops::{Deref, Index, IndexMut}; + +#[derive(Clone, Debug)] +pub enum Value { + Float(f64), + Int(i64), + Str(String), + KeyVal((String, Box)), + Array(Vec), + Null, +} + +/// Reprsents a key in nested arrays. +/// For example, in the array `{"save", text="test"}`, the key is `"save"`. +pub struct Key<'a>(pub &'a str); + +impl<'a> Deref for Key<'a> { + type Target = str; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +const NULL: Value = Value::Null; + +#[allow(dead_code)] +impl Value { + pub fn as_str(&self) -> Option<&str> { + if let Value::Str(s) = self { + Some(s) + } else { + None + } + } + + pub fn as_string(&self) -> Option { + if let Value::Str(s) = self { + Some(s.clone()) + } else { + None + } + } + + /// Find a nested array by key (first value of nested array). + /// If the key is not found, it returns a reference to `NULL`. + /// + /// # Example + /// ```lua + /// { + /// {"save", text="test"}, + /// } + /// ``` + /// for above array, calling `find_array("save")` will return the entire array `{"save", text="test"}`. + pub fn find_array(&self, key: &str) -> &Value { + match self { + Value::Array(arr) => { + for item in arr { + if &item[0] == key { + return item; + } + } + &NULL + } + _ => &NULL, + } + } + + pub fn is_array(&self) -> bool { + matches!(self, Value::Array(_)) + } + + pub fn is_kv(&self) -> bool { + matches!(self, Value::KeyVal(_)) + } + + pub fn kv_key(&self) -> Option<&str> { + if let Value::KeyVal((k, _)) = self { + Some(k) + } else { + None + } + } + + pub fn kv_keys<'a>(&'a self) -> Box + 'a> { + match self { + Value::KeyVal((k, _)) => Box::new(std::iter::once(k.as_str())), + Value::Array(arr) => Box::new(arr.iter().filter_map(|v| v.kv_key())), + _ => Box::new(std::iter::empty()), + } + } + + pub fn members<'a>(&'a self) -> Iter<'a> { + match self { + Value::Array(arr) => Iter { iter: arr.iter() }, + _ => Iter::default(), + } + } + + pub fn members_mut<'a>(&'a mut self) -> IterMut<'a> { + match self { + Value::Array(arr) => IterMut { + iter: arr.iter_mut(), + }, + _ => IterMut::default(), + } + } + + pub fn last_member(&self) -> &Value { + match self { + Value::Array(arr) => arr.last().unwrap_or(&NULL), + _ => &NULL, + } + } +} + +impl Index for Value { + type Output = Value; + + fn index(&self, index: usize) -> &Self::Output { + match self { + Value::Array(arr) => { + if index < arr.len() { + &arr[index] + } else { + &NULL + } + } + _ => &NULL, + } + } +} + +impl IndexMut for Value { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + match self { + Value::Array(arr) => { + if index < arr.len() { + &mut arr[index] + } else { + arr.push(NULL); + arr.last_mut().unwrap() + } + } + _ => { + *self = Value::Array(vec![NULL]); + self.index_mut(0) + } + } + } +} + +impl<'a> Index<&'a str> for Value { + type Output = Value; + + fn index(&self, key: &'a str) -> &Self::Output { + match self { + Value::KeyVal((k, v)) if k == key => v, + Value::Array(arr) => { + for item in arr.iter().rev() { + if let Value::KeyVal((k, v)) = item { + if k == key { + return v; + } + } + } + &NULL + } + _ => &NULL, + } + } +} + +impl<'a> IndexMut<&'a str> for Value { + fn index_mut(&mut self, index: &'a str) -> &mut Self::Output { + match &self { + Value::KeyVal((k, _)) => { + if k == index { + if let Value::KeyVal((_, v)) = self { + v + } else { + unreachable!() + } + } else { + *self = Value::KeyVal((index.to_string(), Box::new(NULL))); + if let Value::KeyVal((_, v)) = self { + v + } else { + unreachable!() + } + } + } + Value::Array(arr) => { + for (i, item) in arr.iter().enumerate().rev() { + if let Value::KeyVal((k, _)) = item { + if k == index { + if let Value::KeyVal((_, v)) = &mut self[i] { + return v; + } else { + unreachable!() + } + } + } + } + if let Value::Array(arr) = self { + arr.push(Value::KeyVal((index.to_string(), Box::new(NULL)))); + if let Value::KeyVal((_, v)) = arr.last_mut().unwrap() { + v + } else { + unreachable!() + } + } else { + unreachable!() + } + } + _ => { + *self = Value::Array(vec![Value::KeyVal((index.to_string(), Box::new(NULL)))]); + self.index_mut(index) + } + } + } +} + +impl<'a> Index<&'a String> for Value { + type Output = Value; + + #[inline(always)] + fn index(&self, key: &'a String) -> &Self::Output { + self.index(key.as_str()) + } +} + +impl<'a> IndexMut<&'a String> for Value { + #[inline(always)] + fn index_mut(&mut self, index: &'a String) -> &mut Self::Output { + self.index_mut(index.as_str()) + } +} + +impl Index for Value { + type Output = Value; + + #[inline(always)] + fn index(&self, key: String) -> &Self::Output { + self.index(key.as_str()) + } +} + +impl IndexMut for Value { + #[inline(always)] + fn index_mut(&mut self, index: String) -> &mut Self::Output { + self.index_mut(index.as_str()) + } +} + +impl<'a, 'b> Index<&'b Key<'a>> for Value { + type Output = Value; + + #[inline(always)] + fn index(&self, key: &'b Key<'a>) -> &Self::Output { + self.find_array(&key.0) + } +} + +impl<'a> Index> for Value { + type Output = Value; + + #[inline(always)] + fn index(&self, key: Key<'a>) -> &Self::Output { + self.find_array(&key.0) + } +} + +impl PartialEq for Value { + fn eq(&self, other: &str) -> bool { + match self { + Value::Str(s) => s == other, + _ => false, + } + } +} + +impl PartialEq for Value { + fn eq(&self, other: &String) -> bool { + self == other.as_str() + } +} + +impl PartialEq for Value { + fn eq(&self, other: &i64) -> bool { + match self { + Value::Int(i) => i == other, + _ => false, + } + } +} + +impl PartialEq for Value { + fn eq(&self, other: &f64) -> bool { + match self { + Value::Float(f) => f == other, + _ => false, + } + } +} + +impl PartialOrd for Value { + fn partial_cmp(&self, other: &i64) -> Option { + match self { + Value::Int(i) => i.partial_cmp(other), + _ => None, + } + } +} + +impl PartialOrd for Value { + fn partial_cmp(&self, other: &f64) -> Option { + match self { + Value::Float(f) => f.partial_cmp(other), + _ => None, + } + } +} + +#[derive(Default)] +pub struct Iter<'a> { + iter: std::slice::Iter<'a, Value>, +} + +impl<'a> Iterator for Iter<'a> { + type Item = &'a Value; + + #[inline(always)] + fn next(&mut self) -> Option { + self.iter.next() + } +} + +impl<'a> ExactSizeIterator for Iter<'a> { + #[inline(always)] + fn len(&self) -> usize { + self.iter.len() + } +} + +impl<'a> DoubleEndedIterator for Iter<'a> { + #[inline(always)] + fn next_back(&mut self) -> Option { + self.iter.next_back() + } +} + +#[derive(Default)] +pub struct IterMut<'a> { + iter: std::slice::IterMut<'a, Value>, +} + +impl<'a> Iterator for IterMut<'a> { + type Item = &'a mut Value; + + #[inline(always)] + fn next(&mut self) -> Option { + self.iter.next() + } +} + +impl<'a> ExactSizeIterator for IterMut<'a> { + #[inline(always)] + fn len(&self) -> usize { + self.iter.len() + } +} + +impl<'a> DoubleEndedIterator for IterMut<'a> { + #[inline(always)] + fn next_back(&mut self) -> Option { + self.iter.next_back() + } +} + +#[derive(Clone, Debug)] +pub struct AstFile { + pub astver: f64, + pub astname: Option, + pub ast: Value, +} diff --git a/src/scripts/artemis/mod.rs b/src/scripts/artemis/mod.rs index 46b8941..de3b312 100644 --- a/src/scripts/artemis/mod.rs +++ b/src/scripts/artemis/mod.rs @@ -1,2 +1,3 @@ #[cfg(feature = "artemis-arc")] pub mod archive; +pub mod ast; diff --git a/src/scripts/mod.rs b/src/scripts/mod.rs index b2da791..7b1596b 100644 --- a/src/scripts/mod.rs +++ b/src/scripts/mod.rs @@ -72,6 +72,8 @@ lazy_static::lazy_static! { Box::new(cat_system::cst::CstScriptBuilder::new()), #[cfg(feature = "artemis-arc")] Box::new(artemis::archive::pfs::ArtemisArcBuilder::new()), + #[cfg(feature = "artemis")] + Box::new(artemis::ast::AstScriptBuilder::new()), ]; pub static ref ALL_EXTS: Vec = BUILDER.iter().flat_map(|b| b.extensions()).map(|s| s.to_string()).collect(); diff --git a/src/types.rs b/src/types.rs index 28b75ac..499ad7e 100644 --- a/src/types.rs +++ b/src/types.rs @@ -225,11 +225,20 @@ pub struct ExtraConfig { pub kirikiri_pimg_overlay: Option, #[cfg(feature = "artemis-arc")] pub artemis_arc_disable_xor: bool, + #[cfg(feature = "artemis")] + pub artemis_indent: Option, + #[cfg(feature = "artemis")] + pub artemis_no_indent: bool, + #[cfg(feature = "artemis")] + pub artemis_max_line_width: usize, } #[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)] /// Script type pub enum ScriptType { + #[cfg(feature = "artemis")] + /// Artemis Engine AST script + Artemis, #[cfg(feature = "artemis-arc")] #[value(alias("pfs"))] /// Artemis archive (pfs)