mirror of
https://github.com/lifegpc/msg-tool.git
synced 2026-06-07 21:38:58 +08:00
Add artemis ast script export support
This commit is contained in:
244
src/scripts/artemis/ast/dump.rs
Normal file
244
src/scripts/artemis/ast/dump.rs
Normal file
@@ -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<dyn Write + 'a>,
|
||||
indent: Option<usize>,
|
||||
max_line_width: usize,
|
||||
current_line_width: usize,
|
||||
}
|
||||
|
||||
impl<'a> Dumper<'a> {
|
||||
pub fn new<W: Write + 'a>(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(())
|
||||
}
|
||||
}
|
||||
156
src/scripts/artemis/ast/mod.rs
Normal file
156
src/scripts/artemis/ast/mod.rs
Normal file
@@ -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<u8>,
|
||||
_filename: &str,
|
||||
encoding: Encoding,
|
||||
_archive_encoding: Encoding,
|
||||
config: &ExtraConfig,
|
||||
) -> Result<Box<dyn Script>> {
|
||||
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<usize>,
|
||||
max_line_width: usize,
|
||||
no_indent: bool,
|
||||
}
|
||||
|
||||
impl AstScript {
|
||||
pub fn new(buf: Vec<u8>, encoding: Encoding, config: &ExtraConfig) -> Result<Self> {
|
||||
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<Vec<Message>> {
|
||||
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("<rt2>", "\n")
|
||||
.replace("<ret2>", "\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<Message>,
|
||||
mut file: Box<dyn WriteSeek + 'a>,
|
||||
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(())
|
||||
}
|
||||
}
|
||||
284
src/scripts/artemis/ast/parser.rs
Normal file
284
src/scripts/artemis/ast/parser.rs
Normal file
@@ -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<S: AsRef<[u8]>>(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<AstFile> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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<f64> {
|
||||
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<String> {
|
||||
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<u8> {
|
||||
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<u8> {
|
||||
if self.pos < self.len {
|
||||
Some(self.str[self.pos])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_key_val(&mut self) -> Result<Value> {
|
||||
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<String> {
|
||||
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<T>(&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<T, A>(&self, msg: T) -> Result<A>
|
||||
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
|
||||
)))
|
||||
}
|
||||
}
|
||||
118
src/scripts/artemis/ast/text.rs
Normal file
118
src/scripts/artemis/ast/text.rs
Normal file
@@ -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<String> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
387
src/scripts/artemis/ast/types.rs
Normal file
387
src/scripts/artemis/ast/types.rs
Normal file
@@ -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<Value>)),
|
||||
Array(Vec<Value>),
|
||||
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<String> {
|
||||
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<dyn Iterator<Item = &'a str> + '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<usize> 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<usize> 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<String> for Value {
|
||||
type Output = Value;
|
||||
|
||||
#[inline(always)]
|
||||
fn index(&self, key: String) -> &Self::Output {
|
||||
self.index(key.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexMut<String> 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<Key<'a>> for Value {
|
||||
type Output = Value;
|
||||
|
||||
#[inline(always)]
|
||||
fn index(&self, key: Key<'a>) -> &Self::Output {
|
||||
self.find_array(&key.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<str> for Value {
|
||||
fn eq(&self, other: &str) -> bool {
|
||||
match self {
|
||||
Value::Str(s) => s == other,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<String> for Value {
|
||||
fn eq(&self, other: &String) -> bool {
|
||||
self == other.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<i64> for Value {
|
||||
fn eq(&self, other: &i64) -> bool {
|
||||
match self {
|
||||
Value::Int(i) => i == other,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<f64> for Value {
|
||||
fn eq(&self, other: &f64) -> bool {
|
||||
match self {
|
||||
Value::Float(f) => f == other,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd<i64> for Value {
|
||||
fn partial_cmp(&self, other: &i64) -> Option<std::cmp::Ordering> {
|
||||
match self {
|
||||
Value::Int(i) => i.partial_cmp(other),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd<f64> for Value {
|
||||
fn partial_cmp(&self, other: &f64) -> Option<std::cmp::Ordering> {
|
||||
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::Item> {
|
||||
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::Item> {
|
||||
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::Item> {
|
||||
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::Item> {
|
||||
self.iter.next_back()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AstFile {
|
||||
pub astver: f64,
|
||||
pub astname: Option<String>,
|
||||
pub ast: Value,
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
#[cfg(feature = "artemis-arc")]
|
||||
pub mod archive;
|
||||
pub mod ast;
|
||||
|
||||
@@ -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<String> =
|
||||
BUILDER.iter().flat_map(|b| b.extensions()).map(|s| s.to_string()).collect();
|
||||
|
||||
Reference in New Issue
Block a user