pub mod compilesettings;
mod messages;
pub mod properties;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::fmt::{Debug, Display};
use std::str::FromStr;
use serde::Serialize;
use strum::{EnumMessage, IntoEnumIterator};
use uuid::Uuid;
use crate::{
errors::{ParserContext, ProjectError},
files::common::ObjectReference,
files::project::{
compilesettings::CompilationType,
messages::{report_parameter_error, DummyEnumType, ParameterErrorKind},
properties::{CompileTargetType, ProjectProperties},
},
io::{Comparator, SourceFile, SourceStream},
parsers::ParseResult,
};
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Default)]
pub struct ProjectFile<'a> {
pub project_type: CompileTargetType,
references: Vec<ProjectReference<'a>>,
objects: Vec<ObjectReference>,
modules: Vec<ProjectModuleReference<'a>>,
classes: Vec<ProjectClassReference<'a>>,
related_documents: Vec<&'a str>,
property_pages: Vec<&'a str>,
designers: Vec<&'a str>,
forms: Vec<&'a str>,
user_controls: Vec<&'a str>,
user_documents: Vec<&'a str>,
pub other_properties: HashMap<&'a str, HashMap<&'a str, &'a str>>,
pub properties: ProjectProperties<'a>,
}
impl Display for ProjectFile<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "VB6 Project File: Type={:?}, References={}, Objects={}, Modules={}, Classes={}, Forms={}, UserControls={}, UserDocuments={}, RelatedDocuments={}, PropertyPages={}",
self.project_type,
self.references.len(),
self.objects.len(),
self.modules.len(),
self.classes.len(),
self.forms.len(),
self.user_controls.len(),
self.user_documents.len(),
self.related_documents.len(),
self.property_pages.len()
)
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
pub enum ProjectReference<'a> {
Compiled {
uuid: Uuid,
unknown1: &'a str,
unknown2: &'a str,
path: &'a str,
description: &'a str,
},
SubProject {
path: &'a str,
},
}
impl Display for ProjectReference<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ProjectReference::Compiled {
uuid,
unknown1,
unknown2,
path,
description,
} => write!(
f,
"Compiled Reference: UUID={uuid}, Unknown1='{unknown1}', Unknown2='{unknown2}', Path='{path}', Description='{description}'"
),
ProjectReference::SubProject { path } => {
write!(f, "Sub-Project Reference: Path='{path}'")
}
}
}
}
impl Serialize for ProjectReference<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeStruct;
match self {
ProjectReference::Compiled {
uuid,
unknown1,
unknown2,
path,
description,
} => {
let mut state = serializer.serialize_struct("CompiledReference", 5)?;
state.serialize_field("uuid", &uuid.to_string())?;
state.serialize_field("unknown1", unknown1)?;
state.serialize_field("unknown2", unknown2)?;
state.serialize_field("path", path)?;
state.serialize_field("description", description)?;
state.end()
}
ProjectReference::SubProject { path } => {
let mut state = serializer.serialize_struct("SubProjectReference", 1)?;
state.serialize_field("path", path)?;
state.end()
}
}
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Hash)]
pub struct ProjectModuleReference<'a> {
pub name: &'a str,
pub path: &'a str,
}
impl Display for ProjectModuleReference<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Module Reference: Name='{}', Path='{}'",
self.name, self.path
)
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Hash)]
pub struct ProjectClassReference<'a> {
pub name: &'a str,
pub path: &'a str,
}
impl Display for ProjectClassReference<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Class Reference: Name='{}', Path='{}'",
self.name, self.path
)
}
}
pub type ProjectResult<'a> = ParseResult<'a, ProjectFile<'a>>;
impl<'a> ProjectFile<'a> {
fn new_empty() -> Self {
ProjectFile {
project_type: CompileTargetType::Exe,
references: vec![],
objects: vec![],
modules: vec![],
classes: vec![],
designers: vec![],
forms: vec![],
user_controls: vec![],
user_documents: vec![],
related_documents: vec![],
property_pages: vec![],
other_properties: HashMap::new(),
properties: ProjectProperties {
compilation_type: CompilationType::default(),
..Default::default()
},
}
}
pub fn references(&self) -> impl Iterator<Item = &ProjectReference<'a>> {
self.references.iter()
}
pub fn modules(&self) -> impl Iterator<Item = &ProjectModuleReference<'a>> {
self.modules.iter()
}
pub fn classes(&self) -> impl Iterator<Item = &ProjectClassReference<'a>> {
self.classes.iter()
}
pub fn objects(&self) -> impl Iterator<Item = &ObjectReference> {
self.objects.iter()
}
pub fn forms(&self) -> impl Iterator<Item = &&'a str> {
self.forms.iter()
}
pub fn user_controls(&self) -> impl Iterator<Item = &&'a str> {
self.user_controls.iter()
}
pub fn user_documents(&self) -> impl Iterator<Item = &&'a str> {
self.user_documents.iter()
}
pub fn designers(&self) -> impl Iterator<Item = &&'a str> {
self.designers.iter()
}
pub fn related_documents(&self) -> impl Iterator<Item = &&'a str> {
self.related_documents.iter()
}
pub fn property_pages(&self) -> impl Iterator<Item = &&'a str> {
self.property_pages.iter()
}
#[must_use]
pub fn other_properties(&self) -> &HashMap<&'a str, HashMap<&'a str, &'a str>> {
&self.other_properties
}
#[must_use]
pub fn parse(source_file: &'a SourceFile) -> ProjectResult<'a> {
let mut project = ProjectFile::new_empty();
let mut input = source_file.source_stream();
let mut other_property_group: Option<&str> = None;
let mut ctx = ParserContext::new(input.file_name(), input.contents);
let handlers = PropertyHandlers::new();
while !input.is_empty() {
if skip_empty_lines(&mut input) {
continue;
}
let line_start = input.start_of_line();
match parse_section_header_line(&mut ctx, &mut input) {
Some(SectionHeaderDetection::HeaderName(section_header)) => {
handle_section_header(
&mut ctx,
&mut project,
section_header,
&mut other_property_group,
);
continue;
}
Some(SectionHeaderDetection::MalformedHeader) => {
continue;
}
None => {
}
}
let Some(property_name) = parse_property_name(&mut ctx, &mut input) else {
continue;
};
if let Some(group) = other_property_group {
handle_other_property(&mut ctx, &mut input, &mut project, group, property_name);
continue;
}
if !handlers.handle(&mut ctx, &mut input, &mut project, property_name) {
handle_unknown_property(&mut ctx, &mut input, line_start, property_name);
}
}
ParseResult::new(Some(project), ctx.into_errors())
}
#[must_use]
pub fn subproject_references(&self) -> Vec<&ProjectReference<'a>> {
self.references
.iter()
.filter(|reference| matches!(reference, ProjectReference::SubProject { .. }))
.collect::<Vec<_>>()
}
#[must_use]
pub fn project_references(&self) -> &Vec<ProjectReference<'a>> {
&self.references
}
#[must_use]
pub fn compiled_references(&self) -> Vec<&ProjectReference<'a>> {
self.references
.iter()
.filter(|reference| matches!(reference, ProjectReference::Compiled { .. }))
.collect::<Vec<_>>()
}
#[must_use]
pub fn subproject_references_mut(&mut self) -> Vec<&mut ProjectReference<'a>> {
self.references
.iter_mut()
.filter(|reference| matches!(reference, ProjectReference::SubProject { .. }))
.collect::<Vec<_>>()
}
#[must_use]
pub fn compiled_references_mut(&mut self) -> Vec<&mut ProjectReference<'a>> {
self.references
.iter_mut()
.filter(|reference| matches!(reference, ProjectReference::Compiled { .. }))
.collect::<Vec<_>>()
}
#[must_use]
pub fn project_references_mut(&mut self) -> &mut Vec<ProjectReference<'a>> {
&mut self.references
}
}
type PropertyHandler<'a> =
fn(&mut ParserContext<'a>, &mut SourceStream<'a>, &mut ProjectFile<'a>, &'a str);
struct PropertyHandlers<'a> {
handlers: HashMap<&'static str, PropertyHandler<'a>>,
}
impl<'a> PropertyHandlers<'a> {
fn new() -> Self {
let mut handlers: HashMap<&'static str, PropertyHandler<'a>> = HashMap::new();
handlers.insert("Type", handle_type);
handlers.insert("Designer", handle_designer);
handlers.insert("Reference", handle_reference);
handlers.insert("Object", handle_object);
handlers.insert("Module", handle_module);
handlers.insert("Class", handle_class);
handlers.insert("Form", handle_form);
handlers.insert("UserControl", handle_user_control);
handlers.insert("UserDocument", handle_user_document);
handlers.insert("RelatedDoc", handle_related_doc);
handlers.insert("PropertyPage", handle_property_page);
handlers.insert("ResFile32", handle_res_file_32);
handlers.insert("IconForm", handle_icon_form);
handlers.insert("Startup", handle_startup);
handlers.insert("HelpFile", handle_help_file);
handlers.insert("Title", handle_title);
handlers.insert("ExeName32", handle_exe_name_32);
handlers.insert("Path32", handle_path_32);
handlers.insert("Command32", handle_command_32);
handlers.insert("Name", handle_name);
handlers.insert("Description", handle_description);
handlers.insert("MajorVer", handle_major_ver);
handlers.insert("MinorVer", handle_minor_ver);
handlers.insert("RevisionVer", handle_revision_ver);
handlers.insert("AutoIncrementVer", handle_auto_increment_ver);
handlers.insert("VersionCompanyName", handle_version_company_name);
handlers.insert("VersionFileDescription", handle_version_file_description);
handlers.insert("VersionLegalCopyright", handle_version_legal_copyright);
handlers.insert("VersionLegalTrademarks", handle_version_legal_trademarks);
handlers.insert("VersionProductName", handle_version_product_name);
handlers.insert("VersionComments", handle_version_comments);
handlers.insert("HelpContextID", handle_help_context_id);
handlers.insert("CompatibleMode", handle_compatible_mode);
handlers.insert("VersionCompatible32", handle_version_compatible_32);
handlers.insert("CompatibleEXE32", handle_compatible_exe_32);
handlers.insert("DllBaseAddress", handle_dll_base_address);
handlers.insert("RemoveUnusedControlInfo", handle_remove_unused_control_info);
handlers.insert("CompilationType", handle_compilation_type);
handlers.insert("OptimizationType", handle_optimization_type);
handlers.insert("FavorPentiumPro(tm)", handle_favor_pentium_pro);
handlers.insert("CodeViewDebugInfo", handle_code_view_debug_info);
handlers.insert("NoAliasing", handle_no_aliasing);
handlers.insert("BoundsCheck", handle_bounds_check);
handlers.insert("OverflowCheck", handle_overflow_check);
handlers.insert("FlPointCheck", handle_fl_point_check);
handlers.insert("FDIVCheck", handle_fdiv_check);
handlers.insert("UnroundedFP", handle_unrounded_fp);
handlers.insert("CondComp", handle_cond_comp);
handlers.insert("StartMode", handle_start_mode);
handlers.insert("Unattended", handle_unattended);
handlers.insert("Retained", handle_retained);
handlers.insert("ThreadPerObject", handle_thread_per_object);
handlers.insert("MaxNumberOfThreads", handle_max_number_of_threads);
handlers.insert("ThreadingModel", handle_threading_model);
handlers.insert("DebugStartupComponent", handle_debug_startup_component);
handlers.insert("DebugStartupOption", handle_debug_startup_option);
handlers.insert("UseExistingBrowser", handle_use_existing_browser);
handlers.insert("NoControlUpgrade", handle_no_control_upgrade);
handlers.insert("ServerSupportFiles", handle_server_support_files);
Self { handlers }
}
fn handle(
&self,
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) -> bool {
if let Some(handler) = self.handlers.get(property_name) {
handler(ctx, input, project, property_name);
true
} else {
false
}
}
}
fn skip_empty_lines(input: &mut SourceStream) -> bool {
let _ = input.take_ascii_whitespaces();
input.take_newline().is_some()
}
fn handle_section_header<'a>(
_ctx: &mut ParserContext<'a>,
project: &mut ProjectFile<'a>,
section_header: &'a str,
other_property_group: &mut Option<&'a str>,
) {
if !project.other_properties.contains_key(section_header) {
project
.other_properties
.insert(section_header, HashMap::new());
*other_property_group = Some(section_header);
}
}
fn handle_other_property<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
group: &'a str,
property_name: &'a str,
) {
let Some(property_value) = parse_property_value(ctx, input, property_name) else {
return;
};
if let Some(map) = project.other_properties.get_mut(group) {
map.insert(property_name, property_value);
}
}
fn handle_unknown_property<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
line_start: usize,
property_name: &'a str,
) {
input.forward_to_next_line();
ctx.error(
input.span_at(line_start),
ProjectError::ParameterLineUnknown {
line: property_name.to_string(),
},
);
}
fn handle_type<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(project_type_value) = parse_converted_value(ctx, input, property_name) {
project.project_type = project_type_value;
}
}
fn handle_designer<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(designer) = parse_path_line(ctx, input, property_name) {
project.designers.push(designer);
}
}
fn handle_reference<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
_property_name: &'a str,
) {
if let Some(reference) = parse_reference(ctx, input) {
project.references.push(reference);
}
}
fn handle_object<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
_property_name: &'a str,
) {
if let Some(object) = parse_object(ctx, input) {
project.objects.push(object);
}
}
fn handle_module<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
_property_name: &'a str,
) {
if let Some(module) = parse_module(ctx, input) {
project.modules.push(module);
}
}
fn handle_class<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
_property_name: &'a str,
) {
if let Some(class) = parse_class(ctx, input) {
project.classes.push(class);
}
}
fn handle_form<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(form) = parse_path_line(ctx, input, property_name) {
project.forms.push(form);
}
}
fn handle_user_control<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(user_control) = parse_path_line(ctx, input, property_name) {
project.user_controls.push(user_control);
}
}
fn handle_user_document<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(user_document) = parse_path_line(ctx, input, property_name) {
project.user_documents.push(user_document);
}
}
fn handle_related_doc<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(related_document) = parse_path_line(ctx, input, property_name) {
project.related_documents.push(related_document);
}
}
fn handle_property_page<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(property_page_value) = parse_path_line(ctx, input, property_name) {
project.property_pages.push(property_page_value);
}
}
fn handle_res_file_32<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(res_32_file) = parse_quoted_value(ctx, input, property_name) {
project.properties.res_file_32_path = res_32_file;
}
}
fn handle_icon_form<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(icon_form_value) = parse_quoted_value(ctx, input, property_name) {
project.properties.icon_form = icon_form_value;
}
}
fn handle_startup<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(startup_value) = parse_optional_quoted_value(ctx, input, property_name) {
project.properties.startup = startup_value;
}
}
fn handle_help_file<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(help_file) = parse_quoted_value(ctx, input, property_name) {
project.properties.help_file_path = help_file;
}
}
fn handle_title<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(title_value) = parse_quoted_value(ctx, input, property_name) {
project.properties.title = title_value;
}
}
fn handle_exe_name_32<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(exe_32_file_name_value) = parse_quoted_value(ctx, input, property_name) {
project.properties.exe_32_file_name = exe_32_file_name_value;
}
}
fn handle_path_32<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(path_32_value) = parse_quoted_value(ctx, input, property_name) {
project.properties.path_32 = path_32_value;
}
}
fn handle_command_32<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(command_line_arguments_value) =
parse_optional_quoted_value(ctx, input, property_name)
{
project.properties.command_line_arguments = command_line_arguments_value;
}
}
fn handle_name<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(name_value) = parse_optional_quoted_value(ctx, input, property_name) {
project.properties.name = name_value;
}
}
fn handle_description<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(description_value) = parse_quoted_value(ctx, input, property_name) {
project.properties.description = description_value;
}
}
fn handle_major_ver<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(major_value) = parse_numeric(ctx, input, property_name) {
project.properties.version_info.major = major_value;
}
}
fn handle_minor_ver<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(minor_value) = parse_numeric(ctx, input, property_name) {
project.properties.version_info.minor = minor_value;
}
}
fn handle_revision_ver<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(revision_value) = parse_numeric(ctx, input, property_name) {
project.properties.version_info.revision = revision_value;
}
}
fn handle_auto_increment_ver<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(auto_increment_revision_value) = parse_numeric(ctx, input, property_name) {
project.properties.version_info.auto_increment_revision = auto_increment_revision_value;
}
}
fn handle_version_company_name<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(company_name_value) = parse_quoted_value(ctx, input, property_name) {
project.properties.version_info.company_name = company_name_value;
}
}
fn handle_version_file_description<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(file_description_value) = parse_quoted_value(ctx, input, property_name) {
project.properties.version_info.file_description = file_description_value;
}
}
fn handle_version_legal_copyright<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(copyright_value) = parse_quoted_value(ctx, input, property_name) {
project.properties.version_info.copyright = copyright_value;
}
}
fn handle_version_legal_trademarks<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(trademark_value) = parse_quoted_value(ctx, input, property_name) {
project.properties.version_info.trademark = trademark_value;
}
}
fn handle_version_product_name<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(product_name_value) = parse_quoted_value(ctx, input, property_name) {
project.properties.version_info.product_name = product_name_value;
}
}
fn handle_version_comments<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(comments_value) = parse_quoted_value(ctx, input, property_name) {
project.properties.version_info.comments = comments_value;
}
}
fn handle_help_context_id<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(help_context_id_value) = parse_quoted_value(ctx, input, property_name) {
project.properties.help_context_id = help_context_id_value;
}
}
fn handle_compatible_mode<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(compatibility_mode_value) = parse_quoted_converted_value(ctx, input, property_name)
{
project.properties.compatibility_mode = compatibility_mode_value;
}
}
fn handle_version_compatible_32<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(version_32_compatibility_value) = parse_quoted_value(ctx, input, property_name) {
project.properties.version_32_compatibility = version_32_compatibility_value;
}
}
fn handle_compatible_exe_32<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(exe_32_compatible_value) = parse_quoted_value(ctx, input, property_name) {
project.properties.exe_32_compatible = exe_32_compatible_value;
}
}
fn handle_dll_base_address<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
_property_name: &'a str,
) {
if let Some(dll_base_address_value) = parse_dll_base_address(ctx, input) {
project.properties.dll_base_address = dll_base_address_value;
}
}
fn handle_remove_unused_control_info<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(unused_control_info_value) = parse_converted_value(ctx, input, property_name) {
project.properties.unused_control_info = unused_control_info_value;
}
}
fn handle_compilation_type<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(compilation_type) = parse_numeric(ctx, input, property_name) {
project.properties.compilation_type = compilation_type;
}
}
fn handle_optimization_type<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(optimization_type_value) = parse_converted_value(ctx, input, property_name) {
project.properties.compilation_type = project
.properties
.compilation_type
.update_optimization_type(optimization_type_value);
}
}
fn handle_favor_pentium_pro<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(favor_pentium_pro_value) = parse_converted_value(ctx, input, property_name) {
project.properties.compilation_type = project
.properties
.compilation_type
.update_favor_pentium_pro(favor_pentium_pro_value);
}
}
fn handle_code_view_debug_info<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(code_view_debug_info_value) = parse_converted_value(ctx, input, property_name) {
project.properties.compilation_type = project
.properties
.compilation_type
.update_code_view_debug_info(code_view_debug_info_value);
}
}
fn handle_no_aliasing<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(aliasing_value) = parse_converted_value(ctx, input, property_name) {
project.properties.compilation_type = project
.properties
.compilation_type
.update_aliasing(aliasing_value);
}
}
fn handle_bounds_check<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(bounds_check_value) = parse_converted_value(ctx, input, property_name) {
project.properties.compilation_type = project
.properties
.compilation_type
.update_bounds_check(bounds_check_value);
}
}
fn handle_overflow_check<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(overflow_check_value) = parse_converted_value(ctx, input, property_name) {
project.properties.compilation_type = project
.properties
.compilation_type
.update_overflow_check(overflow_check_value);
}
}
fn handle_fl_point_check<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(floating_point_check_value) = parse_converted_value(ctx, input, property_name) {
project.properties.compilation_type = project
.properties
.compilation_type
.update_floating_point_check(floating_point_check_value);
}
}
fn handle_fdiv_check<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(pentium_fdiv_bug_check_value) = parse_converted_value(ctx, input, property_name) {
project.properties.compilation_type = project
.properties
.compilation_type
.update_pentium_fdiv_bug_check(pentium_fdiv_bug_check_value);
}
}
fn handle_unrounded_fp<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(unrounded_floating_point_value) = parse_converted_value(ctx, input, property_name) {
project.properties.compilation_type = project
.properties
.compilation_type
.update_unrounded_floating_point(unrounded_floating_point_value);
}
}
fn handle_cond_comp<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(conditional_compile_value) = parse_quoted_value(ctx, input, property_name) {
project.properties.conditional_compile = conditional_compile_value;
}
}
fn handle_start_mode<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(start_mode_value) = parse_converted_value(ctx, input, property_name) {
project.properties.start_mode = start_mode_value;
}
}
fn handle_unattended<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(unattended_value) = parse_converted_value(ctx, input, property_name) {
project.properties.unattended = unattended_value;
}
}
fn handle_retained<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(retained_value) = parse_converted_value(ctx, input, property_name) {
project.properties.retained = retained_value;
}
}
fn handle_thread_per_object<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(thread_per_object_value) = parse_numeric::<i16>(ctx, input, property_name) {
if thread_per_object_value <= 0 {
project.properties.thread_per_object = 0;
} else {
project.properties.thread_per_object = thread_per_object_value.cast_unsigned();
}
}
}
fn handle_max_number_of_threads<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(max_number_of_threads_value) = parse_numeric(ctx, input, property_name) {
project.properties.max_number_of_threads = max_number_of_threads_value;
}
}
fn handle_threading_model<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(threading_model_value) = parse_converted_value(ctx, input, property_name) {
project.properties.threading_model = threading_model_value;
}
}
fn handle_debug_startup_component<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(debug_startup_component_value) = parse_path_line(ctx, input, property_name) {
project.properties.debug_startup_component = debug_startup_component_value;
}
}
fn handle_debug_startup_option<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(debug_startup_option_value) = parse_converted_value(ctx, input, property_name) {
project.properties.debug_startup_option = debug_startup_option_value;
}
}
fn handle_use_existing_browser<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(use_existing_browser_value) = parse_converted_value(ctx, input, property_name) {
project.properties.use_existing_browser = use_existing_browser_value;
}
}
fn handle_no_control_upgrade<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(upgrade_controls_value) = parse_converted_value(ctx, input, property_name) {
project.properties.upgrade_controls = upgrade_controls_value;
}
}
fn handle_server_support_files<'a>(
ctx: &mut ParserContext,
input: &mut SourceStream<'a>,
project: &mut ProjectFile<'a>,
property_name: &'a str,
) {
if let Some(server_support_files_value) = parse_converted_value(ctx, input, property_name) {
project.properties.server_support_files = server_support_files_value;
}
}
enum SectionHeaderDetection<'a> {
HeaderName(&'a str),
MalformedHeader,
}
fn parse_section_header_line<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
) -> Option<SectionHeaderDetection<'a>> {
let line_start = input.start_of_line();
let _header_start = input.take("[", Comparator::CaseSensitive)?;
let Some((other_property, _)) = input.take_until("]", Comparator::CaseSensitive) else {
let unterminated_value = input.take_until_newline().map_or("", |(v, _)| v);
report_parameter_error::<DummyEnumType>(
ctx,
input,
"",
line_start + 1, &ParameterErrorKind::UnterminatedSectionHeader {
value: unterminated_value,
},
);
input.forward_to_next_line();
return Some(SectionHeaderDetection::MalformedHeader);
};
let _ = input.take("]", Comparator::CaseSensitive);
input.forward_to_next_line();
Some(SectionHeaderDetection::HeaderName(other_property))
}
fn parse_property_name<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
) -> Option<&'a str> {
let line_start = input.start_of_line();
let property_name = input.take_until("=", Comparator::CaseSensitive);
match property_name {
None => {
report_parameter_error::<DummyEnumType>(
ctx,
input,
"",
line_start,
&ParameterErrorKind::PropertyNameNotFound,
);
input.forward_to_next_line();
None
}
Some((property_name, _)) => {
let _ = input.take("=", Comparator::CaseSensitive);
Some(property_name)
}
}
}
fn parse_property_value<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
line_type: &'a str,
) -> Option<&'a str> {
let parameter_start = input.offset();
let Some((parameter_value, _)) = input.take_until_newline() else {
report_parameter_error::<DummyEnumType>(
ctx,
input,
line_type,
parameter_start,
&ParameterErrorKind::MissingValueEof,
);
return None;
};
if parameter_value.is_empty() {
report_parameter_error::<DummyEnumType>(
ctx,
input,
line_type,
parameter_start,
&ParameterErrorKind::EmptyValue,
);
return None;
}
Some(parameter_value)
}
fn parse_quoted_value<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
line_type: &'a str,
) -> Option<&'a str> {
let parameter_start = input.offset();
let Some((parameter_value, _)) = input.take_until_newline() else {
report_parameter_error::<DummyEnumType>(
ctx,
input,
line_type,
parameter_start,
&ParameterErrorKind::MissingValueEof,
);
return None;
};
if parameter_value.is_empty() {
report_parameter_error::<DummyEnumType>(
ctx,
input,
line_type,
parameter_start,
&ParameterErrorKind::EmptyValue,
);
return None;
}
let start_quote_found = parameter_value.starts_with('"');
let end_quote_found = parameter_value.ends_with('"');
if !start_quote_found && end_quote_found {
report_parameter_error::<DummyEnumType>(
ctx,
input,
line_type,
parameter_start,
&ParameterErrorKind::MissingOpeningQuote {
value: parameter_value,
},
);
return None;
}
let start_without_end = start_quote_found && !end_quote_found;
let start_with_length_one = start_quote_found && end_quote_found && parameter_value.len() == 1;
if start_without_end || start_with_length_one {
report_parameter_error::<DummyEnumType>(
ctx,
input,
line_type,
parameter_start,
&ParameterErrorKind::MissingClosingQuote {
value: parameter_value,
},
);
return None;
}
if !start_quote_found && !end_quote_found {
report_parameter_error::<DummyEnumType>(
ctx,
input,
line_type,
parameter_start,
&ParameterErrorKind::MissingBothQuotes,
);
return None;
}
let parameter_value = ¶meter_value[1..parameter_value.len() - 1];
Some(parameter_value)
}
fn parse_optional_quoted_value<'a>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
line_type: &'a str,
) -> Option<&'a str> {
let parameter_start = input.offset();
let Some((parameter_value, _)) = input.take_until_newline() else {
report_parameter_error::<DummyEnumType>(
ctx,
input,
line_type,
parameter_start,
&ParameterErrorKind::OptionalMissingValueEof,
);
return None;
};
if parameter_value.is_empty() {
report_parameter_error::<DummyEnumType>(
ctx,
input,
line_type,
parameter_start,
&ParameterErrorKind::EmptyValue,
);
return None;
}
if parameter_value == "\"(None)\""
|| parameter_value == "\"!None!\""
|| parameter_value == "\"!(None)!\""
|| parameter_value == "(None)"
|| parameter_value == "!None!"
|| parameter_value == "!(None)!"
{
return None;
}
let start_quote_found = parameter_value.starts_with('"');
let end_quote_found = parameter_value.ends_with('"');
if !start_quote_found && end_quote_found {
report_parameter_error::<DummyEnumType>(
ctx,
input,
line_type,
parameter_start,
&ParameterErrorKind::MissingOpeningQuote {
value: parameter_value,
},
);
return None;
}
let start_without_end = start_quote_found && !end_quote_found;
let start_with_end_length_one =
start_quote_found && end_quote_found && parameter_value.len() == 1;
if start_without_end || start_with_end_length_one {
report_parameter_error::<DummyEnumType>(
ctx,
input,
line_type,
parameter_start,
&ParameterErrorKind::MissingClosingQuote {
value: parameter_value,
},
);
return None;
}
if !start_quote_found && !end_quote_found {
report_parameter_error::<DummyEnumType>(
ctx,
input,
line_type,
parameter_start,
&ParameterErrorKind::MissingBothQuotes,
);
return None;
}
let parameter_value = ¶meter_value[1..parameter_value.len() - 1];
Some(parameter_value)
}
fn parse_quoted_converted_value<'a, T>(
ctx: &mut ParserContext<'a>,
input: &mut SourceStream<'a>,
line_type: &'a str,
) -> Option<T>
where
T: 'a
+ TryFrom<&'a str, Error = String>
+ IntoEnumIterator
+ EnumMessage
+ Debug
+ Into<i16>
+ Default
+ Copy,
{
let parameter_start = input.offset();
let text_to_newline = input.take_until_newline();
let parameter_value = match text_to_newline {
None => {
report_parameter_error::<T>(
ctx,
input,
line_type,
parameter_start,
&ParameterErrorKind::MissingValueEofWithDefault(std::marker::PhantomData),
);
return None;
}
Some((parameter_value, _)) => parameter_value,
};
let start_quote_found = parameter_value.starts_with('"');
let end_quote_found = parameter_value.ends_with('"');
if !start_quote_found && end_quote_found {
report_parameter_error::<DummyEnumType>(
ctx,
input,
line_type,
parameter_start,
&ParameterErrorKind::MissingOpeningQuote {
value: parameter_value,
},
);
return None;
}
let start_and_end_qoute = start_quote_found && end_quote_found;
let parameter_length_one = parameter_value.len() == 1;
if start_and_end_qoute && parameter_length_one {
report_parameter_error::<T>(
ctx,
input,
line_type,
parameter_start,
&ParameterErrorKind::MissingValueAndClosingQuote {
value: parameter_value,
_phantom: std::marker::PhantomData,
},
);
return None;
}
if start_quote_found && !end_quote_found {
report_parameter_error::<DummyEnumType>(
ctx,
input,
line_type,
parameter_start,
&ParameterErrorKind::MissingClosingQuote {
value: parameter_value,
},
);
return None;
}
if !start_quote_found && !end_quote_found && parameter_length_one {
report_parameter_error::<T>(
ctx,
input,
line_type,
parameter_start,
&ParameterErrorKind::MissingQuotesWithDefault {
value: parameter_value,
_phantom: std::marker::PhantomData,
},
);
return None;
}
if !start_quote_found && !end_quote_found && !parameter_length_one {
report_parameter_error::<T>(
ctx,
input,
line_type,
parameter_start,
&ParameterErrorKind::MissingValueAndQuotes(std::marker::PhantomData),
);
return None;
}
let parameter_value = ¶meter_value[1..parameter_value.len() - 1];
let Ok(value) = T::try_from(parameter_value) else {
report_parameter_error::<T>(
ctx,
input,
line_type,
parameter_start,
&ParameterErrorKind::InvalidValue {
value: parameter_value,
_phantom: std::marker::PhantomData,
},
);
return None;
};
Some(value)
}
fn parse_converted_value<'a, T>(
ctx: &mut ParserContext,
input: &mut SourceStream<'a>,
line_type: &'a str,
) -> Option<T>
where
T: TryFrom<&'a str, Error = String> + IntoEnumIterator + EnumMessage + Debug,
{
let parameter_start = input.offset();
let text_to_newline = input.take_until_newline();
let parameter_value = match text_to_newline {
None => {
ctx.error(
input.span_at(parameter_start),
ProjectError::ParameterValueMissingOpeningQuote {
parameter_line_name: line_type.to_string(),
},
);
return None;
}
Some((parameter_value, _)) => parameter_value,
};
let Ok(value) = T::try_from(parameter_value) else {
let valid_value_message = T::iter()
.map(|v| format!("'{:?}' ({})", v, v.get_message().unwrap()))
.collect::<Vec<_>>()
.join(", ");
ctx.error(
input.span_at(parameter_start),
ProjectError::ParameterValueInvalid {
parameter_line_name: line_type.to_string(),
invalid_value: parameter_value.to_string(),
valid_value_message,
},
);
return None;
};
Some(value)
}
fn parse_numeric<'a, T>(
ctx: &mut ParserContext,
input: &mut SourceStream<'a>,
line_type: &'a str,
) -> Option<T>
where
T: FromStr,
{
let parameter_start = input.offset();
let text_to_newline = input.take_until_newline();
let parameter_value = match text_to_newline {
None => {
ctx.error(
input.span_at(parameter_start),
ProjectError::ParameterValueMissingOpeningQuote {
parameter_line_name: line_type.to_string(),
},
);
return None;
}
Some((parameter_value, _)) => parameter_value,
};
let Ok(value) = parameter_value.parse::<T>() else {
let valid_value_message = format!(
"Failed attempting to parse as {0}. '{parameter_value}' is not a valid {0}",
std::any::type_name::<T>()
);
ctx.error(
input.span_at(parameter_start),
ProjectError::ParameterValueInvalid {
parameter_line_name: line_type.to_string(),
invalid_value: parameter_value.to_string(),
valid_value_message,
},
);
return None;
};
Some(value)
}
fn parse_reference<'a>(
ctx: &mut ParserContext,
input: &mut SourceStream<'a>,
) -> Option<ProjectReference<'a>> {
let reference_start = input.offset();
let compiled_reference_signature = "*\\G{";
if input.peek(compiled_reference_signature.len()) == Some(compiled_reference_signature) {
let _ = input.take(compiled_reference_signature, Comparator::CaseSensitive);
return parse_compiled_reference(ctx, input);
}
let Some((path, _)) = input.take_until_newline() else {
ctx.error(
input.span_at(reference_start),
ProjectError::ReferenceProjectPathNotFound,
);
return None;
};
if path.is_empty() {
ctx.error(
input.span_at(reference_start),
ProjectError::ReferenceProjectPathNotFound,
);
return None;
}
if !path.starts_with("*\\A") {
ctx.error(
input.span_at(reference_start),
ProjectError::ReferenceProjectPathInvalid {
value: path.to_string(),
},
);
return None;
}
let path = &path[3..];
Some(ProjectReference::SubProject { path })
}
fn parse_compiled_reference<'a>(
ctx: &mut ParserContext,
input: &mut SourceStream<'a>,
) -> Option<ProjectReference<'a>> {
let uuid_start = input.offset();
let Some((uuid_text, _)) = input.take_until("}#", Comparator::CaseSensitive) else {
ctx.error(
input.span_at(uuid_start),
ProjectError::ReferenceCompiledUuidMissingMatchingBrace,
);
input.forward_to_next_line();
return None;
};
let Ok(uuid) = Uuid::parse_str(uuid_text) else {
ctx.error(
input.span_at(uuid_start),
ProjectError::ReferenceCompiledUuidInvalid,
);
input.forward_to_next_line();
return None;
};
let _ = input.take("}#", Comparator::CaseSensitive);
let unknown1_start = input.offset();
let Some((unknown1, _)) = input.take_until("#", Comparator::CaseSensitive) else {
ctx.error(
input.span_at(unknown1_start),
ProjectError::ReferenceCompiledUnknown1Missing,
);
input.forward_to_next_line();
return None;
};
let _ = input.take("#", Comparator::CaseSensitive);
let unknown2_start = input.offset();
let Some((unknown2, _)) = input.take_until("#", Comparator::CaseSensitive) else {
ctx.error(
input.span_at(unknown2_start),
ProjectError::ReferenceCompiledUnknown2Missing,
);
input.forward_to_next_line();
return None;
};
let _ = input.take("#", Comparator::CaseSensitive);
let path_start = input.offset();
let Some((path, _)) = input.take_until("#", Comparator::CaseSensitive) else {
ctx.error(
input.span_at(path_start),
ProjectError::ReferenceCompiledPathNotFound,
);
input.forward_to_next_line();
return None;
};
let _ = input.take("#", Comparator::CaseSensitive);
let description_start = input.offset();
let Some((description, _)) = input.take_until_newline() else {
ctx.error(
input.span_at(description_start),
ProjectError::ReferenceCompiledDescriptionNotFound,
);
return None;
};
if description.contains('#') {
ctx.error(
input.span_at(description_start),
ProjectError::ReferenceCompiledDescriptionInvalid,
);
return None;
}
let reference = ProjectReference::Compiled {
uuid,
unknown1,
unknown2,
path,
description,
};
Some(reference)
}
fn parse_object(ctx: &mut ParserContext, input: &mut SourceStream) -> Option<ObjectReference> {
let object_start = input.offset();
let project_object_signature = "\"*\\A";
if input.peek(project_object_signature.len()) == Some(project_object_signature) {
let _ = input.take(project_object_signature, Comparator::CaseSensitive);
let object_path_start = input.offset();
let Some((path, _)) = input.take_until("\"", Comparator::CaseSensitive) else {
ctx.error(
input.span_at(object_path_start),
ProjectError::ObjectProjectPathNotFound,
);
input.forward_to_next_line();
return None;
};
input.forward_to_next_line();
return Some(ObjectReference::Project { path: path.into() });
}
if input.peek(1) != Some("{") {
ctx.error(
input.span_at(object_start),
ProjectError::ObjectCompiledMissingOpeningBrace,
);
input.forward_to_next_line();
return None;
}
let _ = input.take("{", Comparator::CaseSensitive);
let Some((uuid_text, _)) = input.take_until("}", Comparator::CaseSensitive) else {
ctx.error(
input.span_at(object_start),
ProjectError::ObjectCompiledUuidMissingMatchingBrace,
);
input.forward_to_next_line();
return None;
};
let _ = input.take("}", Comparator::CaseSensitive);
let Ok(uuid) = Uuid::parse_str(uuid_text) else {
ctx.error(
input.span_at(object_start),
ProjectError::ObjectCompiledUuidInvalid,
);
input.forward_to_next_line();
return None;
};
let _ = input.take("#", Comparator::CaseSensitive);
let version_start = input.offset();
let Some((version, invalid_version_character)) = input.take_until_not(
&["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "."],
Comparator::CaseSensitive,
) else {
ctx.error(
input.span_at(version_start),
ProjectError::ObjectCompiledVersionMissing,
);
input.forward_to_next_line();
return None;
};
if invalid_version_character != "#" {
ctx.error(
input.span_at(version_start + version.len()),
ProjectError::ObjectCompiledVersionInvalid,
);
input.forward_to_next_line();
return None;
}
let _ = input.take("#", Comparator::CaseSensitive);
let unknown1_start = input.offset();
let Some((unknown1, _)) = input.take_until("; ", Comparator::CaseSensitive) else {
ctx.error(
input.span_at(unknown1_start),
ProjectError::ObjectCompiledUnknown1Missing,
);
input.forward_to_next_line();
return None;
};
let _ = input.take("; ", Comparator::CaseSensitive);
let file_name_start = input.offset();
let file_name = input.take_until_newline();
match file_name {
None => {
ctx.error(
input.span_at(file_name_start),
ProjectError::ObjectCompiledFileNameNotFound,
);
None
}
Some((file_name, _)) => {
if file_name.is_empty() {
ctx.error(
input.span_at(file_name_start),
ProjectError::ObjectCompiledFileNameNotFound,
);
return None;
}
Some(ObjectReference::Compiled {
uuid,
version: version.into(),
unknown1: unknown1.into(),
file_name: file_name.into(),
})
}
}
}
fn parse_module<'a>(
ctx: &mut ParserContext,
input: &mut SourceStream<'a>,
) -> Option<ProjectModuleReference<'a>> {
let module_start = input.offset();
let Some((module_name, _)) = input.take_until("; ", Comparator::CaseSensitive) else {
ctx.error(
input.span_at(module_start),
ProjectError::ModuleNameNotFound,
);
input.forward_to_next_line();
return None;
};
let _ = input.take("; ", Comparator::CaseSensitive);
let module_path_start = input.offset();
let Some((module_path, _)) = input.take_until_newline() else {
ctx.error(
input.span_at(module_path_start),
ProjectError::ModuleFileNameNotFound,
);
return None;
};
if module_path.is_empty() {
ctx.error(
input.span_at(module_path_start),
ProjectError::ModuleFileNameNotFound,
);
return None;
}
let module = ProjectModuleReference {
name: module_name,
path: module_path,
};
Some(module)
}
fn parse_class<'a>(
ctx: &mut ParserContext,
input: &mut SourceStream<'a>,
) -> Option<ProjectClassReference<'a>> {
let class_start = input.offset();
let Some((class_name, _)) = input.take_until("; ", Comparator::CaseSensitive) else {
ctx.error(input.span_at(class_start), ProjectError::ClassNameNotFound);
input.forward_to_next_line();
return None;
};
let _ = input.take("; ", Comparator::CaseSensitive);
let class_path_start = input.offset();
let Some((class_path, _)) = input.take_until_newline() else {
ctx.error(
input.span_at(class_path_start),
ProjectError::ClassFileNameNotFound,
);
return None;
};
if class_path.is_empty() {
ctx.error(
input.span_at(class_path_start),
ProjectError::ClassFileNameNotFound,
);
return None;
}
let class = ProjectClassReference {
name: class_name,
path: class_path,
};
Some(class)
}
fn parse_path_line<'a>(
ctx: &mut ParserContext,
input: &mut SourceStream<'a>,
parameter_line_name: &'a str,
) -> Option<&'a str> {
let path_start = input.offset();
let path_line = input.take_until_newline();
match path_line {
None => {
ctx.error(
input.span_at(path_start),
ProjectError::PathValueNotFound {
parameter_line_name: parameter_line_name.to_string(),
},
);
None
}
Some((file_path, _)) => {
if file_path.is_empty() {
ctx.error(
input.span_at(path_start),
ProjectError::PathValueNotFound {
parameter_line_name: parameter_line_name.to_string(),
},
);
return None;
}
Some(file_path)
}
}
}
fn parse_dll_base_address(ctx: &mut ParserContext, input: &mut SourceStream) -> Option<u32> {
let dll_base_address_start = input.offset();
let Some((base_address_hex_text, _)) = input.take_until_newline() else {
ctx.error(
input.span_at(dll_base_address_start),
ProjectError::DllBaseAddressNotFound,
);
return None;
};
if base_address_hex_text.is_empty() {
ctx.error(
input.span_at(dll_base_address_start),
ProjectError::DllBaseAddressUnparsableEmpty,
);
return None;
}
if !base_address_hex_text.starts_with("&H") {
ctx.error(
input.span_at(dll_base_address_start),
ProjectError::DllBaseAddressMissingHexPrefix,
);
return None;
}
let dll_base_address_start = dll_base_address_start + 2;
let trimmed_base_address_hex_text = base_address_hex_text.trim_start_matches("&H");
let Ok(dll_base_address) = u32::from_str_radix(trimmed_base_address_hex_text, 16) else {
ctx.error(
input.span_at(dll_base_address_start),
ProjectError::DllBaseAddressUnparsable {
hex_value: trimmed_base_address_hex_text.to_string(),
},
);
return None;
};
Some(dll_base_address)
}
#[cfg(test)]
mod tests {
use crate::errors::{ErrorKind, ParserContext, ProjectError};
use crate::files::common::ObjectReference;
use crate::files::project::compilesettings::*;
use crate::files::project::properties::*;
use crate::files::project::ProjectReference;
use crate::io::{Comparator, SourceFile, SourceStream};
use crate::ProjectFile;
use assert_matches::assert_matches;
use uuid::Uuid;
#[test]
fn compatibility_mode_is_no_compatibility() {
use crate::files::project::parse_quoted_converted_value;
let mut input = SourceStream::new("", "CompatibleMode=\"0\"\n");
let parameter_name = input
.take("CompatibleMode", Comparator::CaseSensitive)
.expect("Expected to find 'CompatibleMode' parameter");
let _ = input
.take("=", Comparator::CaseSensitive)
.expect("Expected to find '=' after 'CompatibleMode'");
let mut ctx = ParserContext::new(input.file_name(), input.contents);
let result: Option<CompatibilityMode> =
parse_quoted_converted_value(&mut ctx, &mut input, parameter_name);
let errors = ctx.errors();
assert_eq!(errors.len(), 0);
assert_eq!(result.unwrap(), CompatibilityMode::NoCompatibility);
}
#[test]
fn compatibility_mode_is_project() {
use crate::files::project::parse_quoted_converted_value;
use crate::io::{Comparator, SourceStream};
let mut input = SourceStream::new("", "CompatibleMode=\"1\"\r\n");
let parameter_name = input
.take("CompatibleMode", Comparator::CaseSensitive)
.expect("Expected to find 'CompatibleMode' parameter");
let _ = input
.take("=", Comparator::CaseSensitive)
.expect("Expected to find '=' after 'CompatibleMode'");
let mut ctx = ParserContext::new(input.file_name(), input.contents);
let result: Option<CompatibilityMode> =
parse_quoted_converted_value(&mut ctx, &mut input, parameter_name);
let errors = ctx.errors();
assert_eq!(errors.len(), 0);
assert_eq!(result.unwrap(), CompatibilityMode::Project);
}
#[test]
fn compatibility_mode_is_compatible_exe() {
use crate::files::project::parse_quoted_converted_value;
use crate::io::{Comparator, SourceStream};
let mut input = SourceStream::new("", "CompatibleMode=\"2\"\n");
let parameter_name = input
.take("CompatibleMode", Comparator::CaseSensitive)
.expect("Expected to find 'CompatibleMode' parameter");
let _ = input
.take("=", Comparator::CaseSensitive)
.expect("Expected to find '=' after 'CompatibleMode'");
let mut ctx = ParserContext::new(input.file_name(), input.contents);
let result: Option<CompatibilityMode> =
parse_quoted_converted_value(&mut ctx, &mut input, parameter_name);
let errors = ctx.errors();
assert_eq!(errors.len(), 0);
assert_eq!(result.unwrap(), CompatibilityMode::CompatibleExe);
}
#[test]
fn startup_is_something_exe() {
use crate::files::project::parse_optional_quoted_value;
use crate::io::{Comparator, SourceStream};
let mut input = SourceStream::new("", "Startup=\"something.exe\"\n");
let parameter_name = input
.take("Startup", Comparator::CaseSensitive)
.expect("Expected to find 'Startup' parameter");
let _ = input
.take("=", Comparator::CaseSensitive)
.expect("Expected to find '=' after 'Startup'");
let mut ctx = ParserContext::new(input.file_name(), input.contents);
let result = parse_optional_quoted_value(&mut ctx, &mut input, parameter_name);
let errors = ctx.errors();
assert_eq!(errors.len(), 0);
assert_eq!(result.unwrap(), "something.exe");
}
#[test]
fn project_type_is_exe() {
use crate::files::project::parse_converted_value;
let mut input = SourceStream::new("", "Type=Exe\n");
let parameter_name = input
.take("Type", Comparator::CaseSensitive)
.expect("Expected to find 'Type' parameter");
let _ = input
.take("=", Comparator::CaseSensitive)
.expect("Expected to find '=' after 'Type'");
let mut ctx = ParserContext::new(input.file_name(), input.contents);
let result: Option<CompileTargetType> =
parse_converted_value(&mut ctx, &mut input, parameter_name);
let errors = ctx.errors();
assert_eq!(errors.len(), 0);
assert_eq!(result.unwrap(), CompileTargetType::Exe);
}
#[test]
fn project_type_is_oledll() {
use crate::files::project::parse_converted_value;
let mut input = SourceStream::new("", "Type=OleDll\r\n");
let parameter_name = input
.take("Type", Comparator::CaseSensitive)
.expect("Expected to find 'Type' parameter");
let _ = input
.take("=", Comparator::CaseSensitive)
.expect("Expected to find '=' after 'Type'");
let mut ctx = ParserContext::new(input.file_name(), input.contents);
let result: Option<CompileTargetType> =
parse_converted_value(&mut ctx, &mut input, parameter_name);
assert_eq!(result.unwrap(), CompileTargetType::OleDll);
}
#[test]
fn project_type_is_control() {
use crate::files::project::parse_converted_value;
let mut input = SourceStream::new("", "Type=Control\n");
let parameter_name = input
.take("Type", Comparator::CaseSensitive)
.expect("Expected to find 'Type' parameter");
let _ = input
.take("=", Comparator::CaseSensitive)
.expect("Expected to find '=' after 'Type'");
let mut ctx = ParserContext::new(input.file_name(), input.contents);
let result: Option<CompileTargetType> =
parse_converted_value(&mut ctx, &mut input, parameter_name);
assert_eq!(result.unwrap(), CompileTargetType::Control);
}
#[test]
fn project_type_is_ole_exe() {
use crate::files::project::parse_converted_value;
let mut input = SourceStream::new("", "Type=OleExe\n");
let parameter_name = input
.take("Type", Comparator::CaseSensitive)
.expect("Expected to find 'Type' parameter");
let _ = input
.take("=", Comparator::CaseSensitive)
.expect("Expected to find '=' after 'Type'");
let mut ctx = ParserContext::new(input.file_name(), input.contents);
let result: Option<CompileTargetType> =
parse_converted_value(&mut ctx, &mut input, parameter_name);
assert_eq!(result.unwrap(), CompileTargetType::OleExe);
}
#[test]
fn project_type_is_unknown_type() {
use crate::files::project::parse_converted_value;
let mut input = SourceStream::new("", "Type=blah\r\n");
let parameter_name = input
.take("Type", Comparator::CaseSensitive)
.expect("Expected to find 'Type' parameter");
let _ = input
.take("=", Comparator::CaseSensitive)
.expect("Expected to find '=' after 'Type'");
let mut ctx = ParserContext::new(input.file_name(), input.contents);
let result: Option<CompileTargetType> =
parse_converted_value(&mut ctx, &mut input, parameter_name);
assert!(result.is_none());
let errors = ctx.errors();
assert_eq!(errors.len(), 1);
assert_matches!(
*errors[0].kind,
ErrorKind::Project(ProjectError::ParameterValueInvalid { .. })
);
}
#[test]
fn reference_compiled_line_valid() {
use crate::files::project::parse_reference;
let mut input = SourceStream::new("", "Reference=*\\G{000440D8-E9ED-4435-A9A2-06B05387BB16}#c.0#0#..\\DBCommon\\Libs\\VbIntellisenseFix.dll#VbIntellisenseFix\r\n");
let _ = input
.take("Reference", Comparator::CaseSensitive)
.expect("Expected to find 'Reference' parameter");
let _ = input
.take("=", Comparator::CaseSensitive)
.expect("Expected to find '=' after 'Reference'");
let mut ctx = ParserContext::new(input.file_name(), input.contents);
let result = parse_reference(&mut ctx, &mut input);
let expected_uuid = Uuid::parse_str("000440D8-E9ED-4435-A9A2-06B05387BB16")
.expect("Expected to parse UUID");
assert!(input.is_empty());
let result = result.expect("Expected to successfully parse reference line");
assert_matches!(result, ProjectReference::Compiled { .. });
match result {
ProjectReference::Compiled {
uuid,
unknown1,
unknown2,
path,
description,
} => {
assert_eq!(uuid, expected_uuid);
assert_eq!(unknown1, "c.0");
assert_eq!(unknown2, "0");
assert_eq!(path, r"..\DBCommon\Libs\VbIntellisenseFix.dll");
assert_eq!(description, r"VbIntellisenseFix");
}
ProjectReference::SubProject { .. } => panic!("Expected a compiled reference"),
}
}
#[test]
fn reference_subproject_line_valid() {
use crate::files::project::parse_reference;
let mut input = SourceStream::new("", "Reference=*\\Atest.vbp\r\n");
let _ = input
.take("Reference", Comparator::CaseSensitive)
.expect("Expected to find 'Reference' parameter");
let _ = input
.take("=", Comparator::CaseSensitive)
.expect("Expected to find '=' after 'Reference'");
let mut ctx = ParserContext::new(input.file_name(), input.contents);
let result = parse_reference(&mut ctx, &mut input);
assert!(input.is_empty());
assert_eq!(
result.expect("Expected to successfully parse reference line"),
ProjectReference::SubProject { path: "test.vbp" }
);
}
#[test]
fn module_line_valid() {
use crate::files::project::parse_module;
let mut input = SourceStream::new("", "Module=modDBAssist; ..\\DBCommon\\DBAssist.bas\r\n");
let _ = input
.take("Module", Comparator::CaseSensitive)
.expect("Expected to find 'Module' parameter");
let _ = input
.take("=", Comparator::CaseSensitive)
.expect("Expected to find '=' after 'Module'");
let mut ctx = ParserContext::new(input.file_name(), input.contents);
let result =
parse_module(&mut ctx, &mut input).expect("Expected to successfully parse module line");
assert!(input.is_empty());
assert_eq!(result.name, "modDBAssist");
assert_eq!(result.path, "..\\DBCommon\\DBAssist.bas");
}
#[test]
fn class_line_valid() {
use crate::files::project::parse_class;
let mut input = SourceStream::new(
"",
"Class=CStatusBarClass; ..\\DBCommon\\CStatusBarClass.cls\r\n",
);
let _ = input
.take("Class", Comparator::CaseSensitive)
.expect("Expected to find 'Class' parameter");
let _ = input
.take("=", Comparator::CaseSensitive)
.expect("Expected to find '=' after 'Class'");
let mut ctx = ParserContext::new(input.file_name(), input.contents);
let result =
parse_class(&mut ctx, &mut input).expect("Expected to successfully parse class line");
assert!(input.is_empty());
assert_eq!(result.name, "CStatusBarClass");
assert_eq!(result.path, "..\\DBCommon\\CStatusBarClass.cls");
}
#[test]
fn object_line_valid() {
use crate::files::project::parse_object;
let mut input = SourceStream::new(
"",
"Object={00020430-0000-0000-C000-000000000046}#2.0#0; stdole2.tlb\r\n",
);
let _ = input
.take("Object", Comparator::CaseSensitive)
.expect("Expected to find 'Object' parameter");
let _ = input
.take("=", Comparator::CaseSensitive)
.expect("Expected to find '=' after 'Object'");
let mut ctx = ParserContext::new(input.file_name(), input.contents);
let result = parse_object(&mut ctx, &mut input);
let object = result.expect("Expected to successfully parse object line");
assert!(input.is_empty());
match object {
ObjectReference::Compiled {
uuid,
version,
unknown1,
file_name,
} => {
let expected_uuid = Uuid::parse_str("00020430-0000-0000-C000-000000000046")
.expect("Expected to parse UUID");
assert_eq!(uuid, expected_uuid);
assert_eq!(version, "2.0");
assert_eq!(unknown1, "0");
assert_eq!(file_name, "stdole2.tlb");
}
ObjectReference::Project { .. } => panic!("Expected a compiled object"),
}
}
#[allow(clippy::too_many_lines)]
#[test]
fn thread_per_object_negative() {
let input = r#"Type=Exe
Reference=*\G{00020430-0000-0000-C000-000000000046}#2.0#0#C:\Windows\System32\stdole2.tlb#OLE Automation
Object={00020430-0000-0000-C000-000000000046}#2.0#0; stdole2.tlb
Module=Module1; Module1.bas
Class=Class1; Class1.cls
Form=Form1.frm
Form=Form2.frm
UserControl=UserControl1.ctl
UserDocument=UserDocument1.uds
ExeName32="Project1.exe"
Command32=""
Path32=""
Name="Project1"
HelpContextID="0"
CompatibleMode="0"
MajorVer=1
MinorVer=0
RevisionVer=0
AutoIncrementVer=0
StartMode=0
Unattended=0
Retained=0
ThreadPerObject=-1
MaxNumberOfThreads=1
DebugStartupOption=0
NoControlUpgrade=0
ServerSupportFiles=0
VersionCompanyName="Company Name"
VersionFileDescription="File Description"
VersionLegalCopyright="Copyright"
VersionLegalTrademarks="Trademark"
VersionProductName="Product Name"
VersionComments="Comments"
CompilationType=0
OptimizationType=0
FavorPentiumPro(tm)=0
CodeViewDebugInfo=0
NoAliasing=0
BoundsCheck=0
OverflowCheck=0
FlPointCheck=0
FDIVCheck=0
UnroundedFP=0
CondComp=""
ResFile32=""
IconForm=""
Startup=!(None)!
HelpFile=""
Title="Project1"
[MS Transaction Server]
AutoRefresh=1
"#;
let project_source_file = SourceFile::decode("project1.vbp", input.as_bytes())
.expect("Expected to successfully decode project source file");
let result = ProjectFile::parse(&project_source_file);
let (project_opt, failures) = result.unpack();
if !failures.is_empty() {
for failure in &failures {
failure.print();
}
panic!("Project parse had failures");
}
assert!(failures.is_empty(), "Expected no failures, but found some");
let project = project_opt.expect("Expected to successfully parse project file");
assert_eq!(project.project_type, CompileTargetType::Exe);
assert_eq!(project.references.len(), 1);
assert_eq!(project.objects.len(), 1);
assert_eq!(project.modules.len(), 1);
assert_eq!(project.classes.len(), 1);
assert_eq!(project.designers.len(), 0);
assert_eq!(project.forms.len(), 2);
assert_eq!(project.user_controls.len(), 1);
assert_eq!(project.user_documents.len(), 1);
assert_eq!(
project.properties.upgrade_controls,
UpgradeControls::Upgrade
);
assert_eq!(project.properties.res_file_32_path, "");
assert_eq!(project.properties.icon_form, "");
assert_eq!(project.properties.startup, "");
assert_eq!(project.properties.help_file_path, "");
assert_eq!(project.properties.title, "Project1");
assert_eq!(project.properties.exe_32_file_name, "Project1.exe");
assert_eq!(project.properties.exe_32_compatible, "");
assert_eq!(project.properties.command_line_arguments, "");
assert_eq!(project.properties.path_32, "");
assert_eq!(project.properties.name, "Project1");
assert_eq!(project.properties.help_context_id, "0");
assert_eq!(
project.properties.compatibility_mode,
CompatibilityMode::NoCompatibility
);
assert_eq!(project.properties.version_info.major, 1);
assert_eq!(project.properties.version_info.minor, 0);
assert_eq!(project.properties.version_info.revision, 0);
assert_eq!(project.properties.version_info.auto_increment_revision, 0);
assert_eq!(project.properties.version_info.company_name, "Company Name");
assert_eq!(
project.properties.version_info.file_description,
"File Description"
);
assert_eq!(project.properties.version_info.trademark, "Trademark");
assert_eq!(project.properties.version_info.product_name, "Product Name");
assert_eq!(project.properties.version_info.comments, "Comments");
assert_eq!(
project.properties.server_support_files,
ServerSupportFiles::Local,
"server_support_files check"
);
assert_eq!(project.properties.conditional_compile, "");
assert_matches!(
project.properties.compilation_type,
CompilationType::NativeCode(NativeCodeSettings {
optimization_type: OptimizationType::FavorFastCode,
favor_pentium_pro: FavorPentiumPro::False,
code_view_debug_info: CodeViewDebugInfo::NotCreated,
aliasing: Aliasing::AssumeAliasing,
bounds_check: BoundsCheck::CheckBounds,
overflow_check: OverflowCheck::CheckOverflow,
floating_point_check: FloatingPointErrorCheck::CheckFloatingPointError,
pentium_fdiv_bug_check: PentiumFDivBugCheck::CheckPentiumFDivBug,
unrounded_floating_point: UnroundedFloatingPoint::DoNotAllow
})
);
assert_eq!(project.properties.start_mode, StartMode::StandAlone);
assert_eq!(project.properties.unattended, InteractionMode::Interactive);
assert_eq!(project.properties.retained, Retained::UnloadOnExit);
assert_eq!(project.properties.thread_per_object, 0);
assert_eq!(project.properties.max_number_of_threads, 1);
assert_eq!(
project.properties.debug_startup_option,
DebugStartupOption::WaitForComponentCreation,
"debug_startup_option check"
);
}
#[test]
fn two_line_with_spaces() {
use super::parse_converted_value;
use super::parse_property_name;
use super::parse_reference;
let mut input = SourceStream::new(
"project.vbp",
r"Type=Exe
Reference=*\G{00020430-0000-0000-C000-000000000046}#2.0#0#C:\Windows\System32\stdole2.tlb#OLE Automation",
);
let _ = input.take_ascii_whitespaces();
let mut ctx = ParserContext::new(input.file_name(), input.contents);
let line_type =
parse_property_name(&mut ctx, &mut input).expect("Expected to parse property name");
let type_result: Option<CompileTargetType> =
parse_converted_value(&mut ctx, &mut input, line_type);
assert!(type_result.is_some());
assert_eq!(
type_result.expect("Expected to parse compile target type"),
CompileTargetType::Exe
);
let _ = input.take_ascii_whitespaces();
let _ = parse_property_name(&mut ctx, &mut input).expect("Expected to parse property name");
let reference_result = parse_reference(&mut ctx, &mut input);
assert!(reference_result.is_some());
let reference = reference_result.expect("Expected to parse reference");
assert_eq!(
reference,
ProjectReference::Compiled {
uuid: Uuid::parse_str("00020430-0000-0000-C000-000000000046").unwrap(),
unknown1: "2.0",
unknown2: "0",
path: r"C:\Windows\System32\stdole2.tlb",
description: "OLE Automation",
}
);
}
#[test]
#[allow(clippy::too_many_lines)]
fn no_startup_selected() {
let input = r#"Type=Exe
Reference=*\G{00020430-0000-0000-C000-000000000046}#2.0#0#C:\Windows\System32\stdole2.tlb#OLE Automation
Object={00020430-0000-0000-C000-000000000046}#2.0#0; stdole2.tlb
Module=Module1; Module1.bas
Class=Class1; Class1.cls
Form=Form1.frm
Form=Form2.frm
UserControl=UserControl1.ctl
UserDocument=UserDocument1.uds
ExeName32="Project1.exe"
Command32=""
Path32=""
Name="Project1"
HelpContextID="0"
CompatibleMode="0"
MajorVer=1
MinorVer=0
RevisionVer=0
AutoIncrementVer=0
StartMode=0
Unattended=0
Retained=0
ThreadPerObject=0
MaxNumberOfThreads=1
DebugStartupOption=0
NoControlUpgrade=0
ServerSupportFiles=0
VersionCompanyName="Company Name"
VersionFileDescription="File Description"
VersionLegalCopyright="Copyright"
VersionLegalTrademarks="Trademark"
VersionProductName="Product Name"
VersionComments="Comments"
CompilationType=0
OptimizationType=0
FavorPentiumPro(tm)=0
CodeViewDebugInfo=0
NoAliasing=0
BoundsCheck=0
OverflowCheck=0
FlPointCheck=0
FDIVCheck=0
UnroundedFP=0
CondComp=""
ResFile32=""
IconForm=""
Startup=!(None)!
HelpFile=""
Title="Project1"
[MS Transaction Server]
AutoRefresh=1
"#;
let project_source_file = match SourceFile::decode("project1.vbp", input.as_bytes()) {
Ok(source_file) => source_file,
Err(e) => {
panic!("{}", e.print_to_string().unwrap());
}
};
let result = ProjectFile::parse(&project_source_file);
let (project_opt, failures) = result.unpack();
if !failures.is_empty() {
for failure in &failures {
failure.print();
}
panic!("Project parse had failures");
}
let project = project_opt.expect("Expected to successfully parse project file");
match project.properties.compilation_type {
CompilationType::PCode => {}
CompilationType::NativeCode(val) => {
println!("{:?}", val.pentium_fdiv_bug_check);
}
}
assert_eq!(project.project_type, CompileTargetType::Exe);
assert_eq!(project.references.len(), 1);
assert_eq!(project.objects.len(), 1);
assert_eq!(project.modules.len(), 1);
assert_eq!(project.classes.len(), 1);
assert_eq!(project.designers.len(), 0);
assert_eq!(project.forms.len(), 2);
assert_eq!(project.user_controls.len(), 1);
assert_eq!(project.user_documents.len(), 1);
assert_eq!(
project.properties.upgrade_controls,
UpgradeControls::Upgrade
);
assert_eq!(project.properties.res_file_32_path, "");
assert_eq!(project.properties.icon_form, "");
assert_eq!(project.properties.startup, "");
assert_eq!(project.properties.help_file_path, "");
assert_eq!(project.properties.title, "Project1");
assert_eq!(project.properties.exe_32_file_name, "Project1.exe");
assert_eq!(project.properties.exe_32_compatible, "");
assert_eq!(project.properties.command_line_arguments, "");
assert_eq!(project.properties.path_32, "");
assert_eq!(project.properties.name, "Project1");
assert_eq!(project.properties.help_context_id, "0");
assert_eq!(
project.properties.compatibility_mode,
CompatibilityMode::NoCompatibility
);
assert_eq!(project.properties.version_info.major, 1);
assert_eq!(project.properties.version_info.minor, 0);
assert_eq!(project.properties.version_info.revision, 0);
assert_eq!(project.properties.version_info.auto_increment_revision, 0);
assert_eq!(project.properties.version_info.company_name, "Company Name");
assert_eq!(
project.properties.version_info.file_description,
"File Description"
);
assert_eq!(project.properties.version_info.trademark, "Trademark");
assert_eq!(project.properties.version_info.product_name, "Product Name");
assert_eq!(project.properties.version_info.comments, "Comments");
assert_eq!(
project.properties.server_support_files,
ServerSupportFiles::Local,
"server_support_files check"
);
assert_eq!(project.properties.conditional_compile, "");
assert_eq!(
project.properties.compilation_type,
CompilationType::NativeCode(NativeCodeSettings {
optimization_type: OptimizationType::FavorFastCode,
favor_pentium_pro: FavorPentiumPro::False,
code_view_debug_info: CodeViewDebugInfo::NotCreated,
aliasing: Aliasing::AssumeAliasing,
bounds_check: BoundsCheck::CheckBounds,
overflow_check: OverflowCheck::CheckOverflow,
floating_point_check: FloatingPointErrorCheck::CheckFloatingPointError,
pentium_fdiv_bug_check: PentiumFDivBugCheck::CheckPentiumFDivBug,
unrounded_floating_point: UnroundedFloatingPoint::DoNotAllow,
})
);
assert_eq!(project.properties.start_mode, StartMode::StandAlone);
assert_eq!(project.properties.unattended, InteractionMode::Interactive);
assert_eq!(project.properties.retained, Retained::UnloadOnExit);
assert_eq!(project.properties.thread_per_object, 0);
assert_eq!(project.properties.max_number_of_threads, 1);
assert_eq!(
project.properties.debug_startup_option,
DebugStartupOption::WaitForComponentCreation,
"debug_startup_option check"
);
}
#[test]
#[allow(clippy::too_many_lines)]
fn extra_property_sections() {
let input = r#"Type=Exe
Reference=*\G{00020430-0000-0000-C000-000000000046}#2.0#0#C:\Windows\System32\stdole2.tlb#OLE Automation
Object={00020430-0000-0000-C000-000000000046}#2.0#0; stdole2.tlb
Module=Module1; Module1.bas
Class=Class1; Class1.cls
Form=Form1.frm
Form=Form2.frm
UserControl=UserControl1.ctl
UserDocument=UserDocument1.uds
ExeName32="Project1.exe"
Command32=""
Path32=""
Name="Project1"
HelpContextID="0"
CompatibleMode="0"
MajorVer=1
MinorVer=0
RevisionVer=0
AutoIncrementVer=0
StartMode=0
Unattended=0
Retained=0
ThreadPerObject=0
MaxNumberOfThreads=1
DebugStartupOption=0
NoControlUpgrade=0
ServerSupportFiles=0
VersionCompanyName="Company Name"
VersionFileDescription="File Description"
VersionLegalCopyright="Copyright"
VersionLegalTrademarks="Trademark"
VersionProductName="Product Name"
VersionComments="Comments"
CompilationType=0
OptimizationType=0
FavorPentiumPro(tm)=0
CodeViewDebugInfo=0
NoAliasing=0
BoundsCheck=0
OverflowCheck=0
FlPointCheck=0
FDIVCheck=0
UnroundedFP=0
CondComp=""
ResFile32=""
IconForm=""
Startup=!(None)!
HelpFile=""
Title="Project1"
[MS Transaction Server]
AutoRefresh=1
[VBCompiler]
LinkSwitches=/STACK:32180000
Comment=Nouveaut�s :�- ajout d'options dans le menu du widget��Am�liorations :�- position de la fenetre sauvegard�e��Bugs corrig�s :�- 1.4.12 - L'erreur 383 s'est produite dans la fen�tre frmConfig de la proc�dure TimerStart_Timer � la ligne 780 : Propri�t� 'Text' en lecture seule.�- Position de la fen�tre non restaur� en cas de r�duction auto au d�marrage.
"#;
let project_source_file =
match SourceFile::decode_with_replacement("project1.vbp", input.as_bytes()) {
Ok(source_file) => source_file,
Err(e) => {
e.print();
panic!("failed to decode project source code.");
}
};
let result = ProjectFile::parse(&project_source_file);
let (project_opt, failures) = result.unpack();
if !failures.is_empty() {
for failure in &failures {
failure.print();
}
panic!("Project parse had failures");
}
assert!(failures.is_empty(), "Expected no failures, but found some");
let project = project_opt.expect("Expected to successfully parse project file");
assert_eq!(project.project_type, CompileTargetType::Exe);
assert_eq!(project.references.len(), 1);
assert_eq!(project.objects.len(), 1);
assert_eq!(project.modules.len(), 1);
assert_eq!(project.classes.len(), 1);
assert_eq!(project.designers.len(), 0);
assert_eq!(project.forms.len(), 2);
assert_eq!(project.user_controls.len(), 1);
assert_eq!(project.user_documents.len(), 1);
assert_eq!(project.other_properties.len(), 2);
assert_eq!(
project.properties.upgrade_controls,
UpgradeControls::Upgrade
);
assert_eq!(project.properties.res_file_32_path, "");
assert_eq!(project.properties.icon_form, "");
assert_eq!(project.properties.startup, "");
assert_eq!(project.properties.help_file_path, "");
assert_eq!(project.properties.title, "Project1");
assert_eq!(project.properties.exe_32_file_name, "Project1.exe");
assert_eq!(project.properties.exe_32_compatible, "");
assert_eq!(project.properties.command_line_arguments, "");
assert_eq!(project.properties.path_32, "");
assert_eq!(project.properties.name, "Project1");
assert_eq!(project.properties.help_context_id, "0");
assert_eq!(
project.properties.compatibility_mode,
CompatibilityMode::NoCompatibility
);
assert_eq!(project.properties.version_info.major, 1);
assert_eq!(project.properties.version_info.minor, 0);
assert_eq!(project.properties.version_info.revision, 0);
assert_eq!(project.properties.version_info.auto_increment_revision, 0);
assert_eq!(project.properties.version_info.company_name, "Company Name");
assert_eq!(
project.properties.version_info.file_description,
"File Description"
);
assert_eq!(project.properties.version_info.trademark, "Trademark");
assert_eq!(project.properties.version_info.product_name, "Product Name");
assert_eq!(project.properties.version_info.comments, "Comments");
assert_eq!(
project.properties.server_support_files,
ServerSupportFiles::Local,
"server_support_files check"
);
assert_eq!(project.properties.conditional_compile, "");
assert_eq!(
project.properties.compilation_type,
CompilationType::NativeCode(NativeCodeSettings {
optimization_type: OptimizationType::FavorFastCode,
favor_pentium_pro: FavorPentiumPro::False,
code_view_debug_info: CodeViewDebugInfo::NotCreated,
aliasing: Aliasing::AssumeAliasing,
bounds_check: BoundsCheck::CheckBounds,
overflow_check: OverflowCheck::CheckOverflow,
floating_point_check: FloatingPointErrorCheck::CheckFloatingPointError,
pentium_fdiv_bug_check: PentiumFDivBugCheck::CheckPentiumFDivBug,
unrounded_floating_point: UnroundedFloatingPoint::DoNotAllow,
})
);
assert_eq!(project.properties.start_mode, StartMode::StandAlone);
assert_eq!(project.properties.unattended, InteractionMode::Interactive);
assert_eq!(project.properties.retained, Retained::UnloadOnExit);
assert_eq!(project.properties.thread_per_object, 0);
assert_eq!(project.properties.max_number_of_threads, 1);
assert_eq!(
project.properties.debug_startup_option,
DebugStartupOption::WaitForComponentCreation,
"debug_startup_option check"
);
}
}