pub mod error;
use crate::error::TinyV2Error;
#[derive(Debug, Default)]
pub struct Header<'a> {
pub major_version: u32,
pub minor_version: u32,
pub namespace_a: &'a str,
pub namespace_b: &'a str,
pub namespaces: Vec<&'a str>,
pub properties: Vec<Property<'a>>,
}
#[derive(Debug, Default)]
pub struct Property<'a> {
pub key: &'a str,
pub value: Option<&'a str>,
}
#[derive(Debug, Default)]
pub struct Content<'a> {
pub class_section: Vec<ClassSection<'a>>,
}
#[derive(Debug, Default)]
pub struct ClassSection<'a> {
pub class_name_a: &'a str,
pub class_name_b: Option<&'a str>,
pub extra_ns_class_names: Vec<&'a str>,
pub class_sub_sections: Vec<ClassSubSection<'a>>,
}
#[derive(Debug)]
pub enum ClassSubSection<'a> {
Comment(&'a str),
Field {
field_desc_a: &'a str,
field_name_a: &'a str,
field_name_b: Option<&'a str>,
extra_ns_field_names: Vec<&'a str>,
field_sub_sections: Vec<&'a str>,
},
Method {
method_desc_a: &'a str,
method_name_a: &'a str,
method_name_b: Option<&'a str>,
extra_ns_method_names: Vec<&'a str>,
method_sub_sections: Vec<MethodSubSection<'a>>,
},
}
#[derive(Debug)]
pub enum MethodSubSection<'a> {
Comment(&'a str),
Parameter {
lv_index: u32,
var_name_a: Option<&'a str>,
var_name_b: Option<&'a str>,
extra_ns_var_names: Vec<&'a str>,
method_parameter_sub_sections: Vec<&'a str>,
},
Variable {
lv_index: u32,
lv_start_offset: u32,
optional_lvt_index: i32,
var_name_a: Option<&'a str>,
var_name_b: Option<&'a str>,
extra_ns_var_names: Vec<&'a str>,
method_variable_sub_sections: Vec<&'a str>,
},
}
#[derive(Default)]
struct SectionBuilder<'a> {
class_section: Vec<ClassSection<'a>>,
class_sub_sections: Vec<ClassSubSection<'a>>,
method_member_sub_sections: Vec<&'a str>,
method_sub_sections: Vec<MethodSubSection<'a>>,
properties: Vec<Property<'a>>,
}
#[derive(Debug, Default)]
pub struct File<'a> {
pub header: Header<'a>,
pub content: Content<'a>,
}
impl<'a> File<'a> {
fn from_str(s: &'a str) -> Result<Self, TinyV2Error> {
let mut sections = SectionBuilder::default();
let mut lines = s.lines();
while let Some(line) = lines.next_back() {
let leading_tabs = line.chars().take_while(|c| *c == '\t').count();
let mut words = line.trim_ascii().split('\t');
let identifier = words
.next()
.filter(|s| !s.is_empty())
.ok_or(TinyV2Error::new("Line empty"))?;
match (leading_tabs, identifier) {
(0, "tiny") => {
let major_version = words
.next()
.ok_or(TinyV2Error::new("Major version not found"))?
.parse()
.map_err(|_| {
TinyV2Error::new("Major version must be a non negative integer")
})?;
let minor_version = words
.next()
.ok_or(TinyV2Error::new("Minor version not found"))?
.parse()
.map_err(|_| {
TinyV2Error::new("Minor version must be a non negative integer")
})?;
let namespace_a = words
.next()
.ok_or(TinyV2Error::new("Namespace a not found"))?;
let namespace_b = words
.next()
.ok_or(TinyV2Error::new("Namespace b not found"))?;
let namespaces: Vec<&'a str> = words.collect();
let properties = sections.properties;
return Ok(Self {
content: Content {
class_section: sections.class_section,
},
header: Header {
major_version,
minor_version,
namespace_a,
namespace_b,
namespaces,
properties,
},
});
}
(0, "c") => {
let class_name_a = words
.next()
.filter(|s| !s.is_empty())
.ok_or(TinyV2Error::new("Class name a not found"))?;
let class_name_b = words.next().filter(|s| !s.is_empty());
let extra_ns_class_names = words.collect();
let class_sub_sections = std::mem::take(&mut sections.class_sub_sections);
sections.class_section.push(ClassSection {
class_name_a,
class_name_b,
extra_ns_class_names,
class_sub_sections,
});
}
(1, "c") => sections
.class_sub_sections
.push(ClassSubSection::Comment(words.next().unwrap_or_default())),
(2, "c") => sections
.method_sub_sections
.push(MethodSubSection::Comment(words.next().unwrap_or_default())),
(3, "c") => sections
.method_member_sub_sections
.push(words.next().unwrap_or_default()),
(1, "f") => {
let field_desc_a = words
.next()
.filter(|s| !s.is_empty())
.ok_or(TinyV2Error::new("Field desc a not found"))?;
let field_name_a = words
.next()
.filter(|s| !s.is_empty())
.ok_or(TinyV2Error::new("Field name a not found"))?;
let field_name_b = words.next().filter(|s| !s.is_empty());
let extra_ns_field_names = words.collect();
let field_sub_sections = std::mem::take(&mut sections.method_sub_sections);
let field_sub_sections = field_sub_sections
.into_iter()
.filter_map(|mss| match mss {
MethodSubSection::Comment(c) => Some(c),
_ => None,
})
.collect();
sections.class_sub_sections.push(ClassSubSection::Field {
field_desc_a,
field_name_a,
field_name_b,
extra_ns_field_names,
field_sub_sections,
});
}
(1, "m") => {
let method_desc_a = words
.next()
.filter(|s| !s.is_empty())
.ok_or(TinyV2Error::new("Method desc a not found"))?;
let method_name_a = words
.next()
.filter(|s| !s.is_empty())
.ok_or(TinyV2Error::new("Method name a not found"))?;
let method_name_b = words.next().filter(|s| !s.is_empty());
let extra_ns_method_names = words.collect();
let method_sub_sections = std::mem::take(&mut sections.method_sub_sections);
sections.class_sub_sections.push(ClassSubSection::Method {
method_desc_a,
method_name_a,
method_name_b,
extra_ns_method_names,
method_sub_sections,
});
}
(2, "p") => {
let lv_index = words
.next()
.ok_or(TinyV2Error::new("Method parameter must have a lv-index"))?
.parse()
.map_err(|_| TinyV2Error::new("lv-index must be a non negative int"))?;
let var_name_a = words.next().filter(|s| !s.is_empty());
let var_name_b = words.next().filter(|s| !s.is_empty());
let extra_ns_var_names = words.collect();
let method_parameter_sub_sections =
std::mem::take(&mut sections.method_member_sub_sections);
sections
.method_sub_sections
.push(MethodSubSection::Parameter {
lv_index,
var_name_a,
var_name_b,
extra_ns_var_names,
method_parameter_sub_sections,
});
}
(2, "v") => {
let lv_index = words
.next()
.ok_or(TinyV2Error::new("Method variable must have a lv-index"))?
.parse()
.map_err(|_| TinyV2Error::new("lv-index must be a non negative int"))?;
let lv_start_offset = words
.next()
.ok_or(TinyV2Error::new(
"Method variable must have a lv-start-offset",
))?
.parse()
.map_err(|_| {
TinyV2Error::new("lv-start-offset must be a non negative int")
})?;
let optional_lvt_index = words
.next()
.ok_or(TinyV2Error::new("Method variable must have a lvt-index"))?
.parse()
.map_err(|_| {
TinyV2Error::new("lvt-index must be signed integer with 32 bits")
})?;
if optional_lvt_index < -1 {
return Err(TinyV2Error::new("lvt-index must be positive integer or -1"));
}
let var_name_a = words.next().filter(|s| !s.is_empty());
let var_name_b = words.next().filter(|s| !s.is_empty());
let extra_ns_var_names = words.collect();
let method_variable_sub_sections =
std::mem::take(&mut sections.method_member_sub_sections);
sections
.method_sub_sections
.push(MethodSubSection::Variable {
lv_index,
lv_start_offset,
optional_lvt_index,
var_name_a,
var_name_b,
extra_ns_var_names,
method_variable_sub_sections,
});
}
(1, key) => {
let value = words.next();
sections.properties.push(Property { key, value });
}
(lt, id) => {
return Err(TinyV2Error(format!(
"Wrong indentation or identifier (Tab size: {}, Identifier: {})",
lt, id,
)));
}
}
}
Err(TinyV2Error::new("Header not found!"))
}
}
pub fn from_str(s: &str) -> Result<File<'_>, TinyV2Error> {
File::from_str(s)
}