genie_scx/
ai.rs

1use crate::Result;
2use byteorder::{ReadBytesExt, WriteBytesExt, LE};
3use genie_support::{read_str, write_i32_str, DecodeStringError, ReadStringError};
4use std::convert::TryFrom;
5use std::io::{Read, Write};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, num_enum::IntoPrimitive, num_enum::TryFromPrimitive)]
8#[repr(u32)]
9pub enum AIErrorCode {
10    ///
11    ConstantAlreadyDefined = 0,
12    ///
13    FileOpenFailed = 1,
14    ///
15    FileReadFailed = 2,
16    ///
17    InvalidIdentifier = 3,
18    ///
19    InvalidKeyword = 4,
20    ///
21    InvalidPreprocessorDirective = 5,
22    ///
23    ListFull = 6,
24    ///
25    MissingArrow = 7,
26    ///
27    MissingClosingParenthesis = 8,
28    ///
29    MissingClosingQuote = 9,
30    ///
31    MissingEndIf = 10,
32    ///
33    MissingFileName = 11,
34    ///
35    MissingIdentifier = 12,
36    ///
37    MissingKeyword = 13,
38    ///
39    MissingLHS = 14,
40    ///
41    MissingOpeningParenthesis = 15,
42    ///
43    MissingPreprocessorSymbol = 16,
44    ///
45    MissingRHS = 17,
46    ///
47    NoRules = 18,
48    ///
49    PreprocessorNestingTooDeep = 19,
50    ///
51    RuleTooLong = 20,
52    ///
53    StringTableFull = 21,
54    ///
55    UndocumentedError = 22,
56    ///
57    UnexpectedElse = 23,
58    ///
59    UnexpectedEndIf = 24,
60    ///
61    UnexpectedError = 25,
62    ///
63    UnexpectedEOF = 26,
64}
65
66#[derive(Debug, Clone)]
67pub struct AIErrorInfo {
68    filename: String,
69    line_number: i32,
70    description: String,
71    error_code: AIErrorCode,
72}
73
74fn parse_bytes(bytes: &[u8]) -> std::result::Result<String, ReadStringError> {
75    let mut bytes = bytes.to_vec();
76    if let Some(end) = bytes.iter().position(|&byte| byte == 0) {
77        bytes.truncate(end);
78    }
79    if bytes.is_empty() {
80        Ok("<empty>".to_string())
81    } else {
82        String::from_utf8(bytes).map_err(|_| ReadStringError::DecodeStringError(DecodeStringError))
83    }
84}
85
86impl AIErrorInfo {
87    /// Read AI error information from an input stream.
88    pub fn read_from(mut input: impl Read) -> Result<Self> {
89        // TODO support non UTF8 encoding
90        let mut filename_bytes = [0; 257];
91        input.read_exact(&mut filename_bytes)?;
92        let line_number = input.read_i32::<LE>()?;
93        let mut description_bytes = [0; 128];
94        input.read_exact(&mut description_bytes)?;
95        let error_code = AIErrorCode::try_from(input.read_u32::<LE>()?)?;
96
97        let filename = parse_bytes(&filename_bytes)?;
98        let description = parse_bytes(&description_bytes)?;
99
100        Ok(AIErrorInfo {
101            filename,
102            line_number,
103            description,
104            error_code,
105        })
106    }
107
108    /// Write AI error information to an output stream.
109    pub fn write_to(&self, mut output: impl Write) -> Result<()> {
110        // TODO support non UTF8 encoding
111        let mut filename_bytes = [0; 257];
112        (&mut filename_bytes[..self.filename.len()]).copy_from_slice(self.filename.as_bytes());
113        output.write_all(&filename_bytes)?;
114
115        output.write_i32::<LE>(self.line_number)?;
116
117        let mut description_bytes = [0; 128];
118        (&mut description_bytes[..self.description.len()])
119            .copy_from_slice(self.description.as_bytes());
120        output.write_all(&description_bytes)?;
121
122        output.write_u32::<LE>(self.error_code.into())?;
123
124        Ok(())
125    }
126}
127
128#[derive(Debug, Clone)]
129pub struct AIFile {
130    filename: String,
131    content: String,
132}
133
134impl AIFile {
135    /// Read an embedded AI file from an input stream.
136    pub fn read_from(mut input: impl Read) -> Result<Self> {
137        let len = input.read_i32::<LE>()? as usize;
138        let filename = read_str(&mut input, len)?.expect("missing ai file name");
139        let len = input.read_i32::<LE>()? as usize;
140        let content = read_str(&mut input, len)?.expect("empty ai file?");
141
142        Ok(Self { filename, content })
143    }
144
145    /// Write this embedded AI file to an output stream.
146    pub fn write_to(&self, mut output: impl Write) -> Result<()> {
147        write_i32_str(&mut output, &self.filename)?;
148        write_i32_str(&mut output, &self.content)?;
149        Ok(())
150    }
151}
152
153#[derive(Debug, Default, Clone)]
154pub struct AIInfo {
155    error: Option<AIErrorInfo>,
156    files: Vec<AIFile>,
157}
158
159impl AIInfo {
160    pub fn read_from(mut input: impl Read) -> Result<Option<Self>> {
161        let has_ai_files = input.read_u32::<LE>()? != 0;
162        let has_error = input.read_u32::<LE>()? != 0;
163
164        if !has_error && !has_ai_files {
165            return Ok(None);
166        }
167
168        let error = if has_error {
169            Some(AIErrorInfo::read_from(&mut input)?)
170        } else {
171            None
172        };
173
174        let num_ai_files = input.read_u32::<LE>()?;
175        let mut files = vec![];
176        for _ in 0..num_ai_files {
177            files.push(AIFile::read_from(&mut input)?);
178        }
179
180        Ok(Some(Self { error, files }))
181    }
182
183    pub fn write_to(&self, mut output: impl Write) -> Result<()> {
184        output.write_u32::<LE>(if self.files.is_empty() { 0 } else { 1 })?;
185
186        if let Some(error) = &self.error {
187            output.write_u32::<LE>(1)?;
188            error.write_to(&mut output)?;
189        } else {
190            output.write_u32::<LE>(0)?;
191        }
192
193        if !self.files.is_empty() {
194            output.write_u32::<LE>(self.files.len() as u32)?;
195            for file in &self.files {
196                file.write_to(&mut output)?;
197            }
198        }
199
200        Ok(())
201    }
202}