diff --git a/crates/polymodel-ldraw-core/src/bfc.rs b/crates/polymodel-ldraw-core/src/bfc.rs index 8084a33..f82c71a 100644 --- a/crates/polymodel-ldraw-core/src/bfc.rs +++ b/crates/polymodel-ldraw-core/src/bfc.rs @@ -1,8 +1,8 @@ use crate::model::ModelData; use crate::scanner::{ScannedLine, TokenKind}; use crate::types::{ - add_diag, limit_error, Diagnostic, DiagnosticCode, LdrawLimits, LimitCounters, LimitKind, - ParseError, ParserProfile, Severity, + Diagnostic, DiagnosticCode, LdrawLimits, LimitCounters, LimitKind, ParseError, ParserProfile, + Severity, add_diag, limit_error, }; use serde::{Deserialize, Serialize}; diff --git a/crates/polymodel-ldraw-core/src/geom.rs b/crates/polymodel-ldraw-core/src/geom.rs index 013bf98..de6ee89 100644 --- a/crates/polymodel-ldraw-core/src/geom.rs +++ b/crates/polymodel-ldraw-core/src/geom.rs @@ -2,8 +2,8 @@ use crate::bfc::BfcState; use crate::model::{Include, ModelData}; use crate::scanner::{LineType, ScannedLine, Token}; use crate::types::{ - add_diag, limit_error, Diagnostic, DiagnosticCode, LdrawLimits, LimitCounters, LimitKind, - ParseError, ParserProfile, + Diagnostic, DiagnosticCode, LdrawLimits, LimitCounters, LimitKind, ParseError, ParserProfile, + add_diag, limit_error, }; use serde::{Deserialize, Deserializer, Serialize}; use std::borrow::Cow; @@ -157,8 +157,7 @@ where match Representation::deserialize(deserializer)? { Representation::Typed(bounds) => Ok(bounds), Representation::Legacy(values) => { - let values = values - .map(|value| value.parse::().map_err(serde::de::Error::custom)); + let values = values.map(|value| value.parse::().map_err(serde::de::Error::custom)); let [min_x, min_y, min_z, max_x, max_y, max_z] = values .into_iter() .collect::, _>>()? @@ -203,7 +202,9 @@ pub(crate) enum ParsedPrimitive { pub struct ColourCode(pub u16); impl From for u16 { - fn from(c: ColourCode) -> u16 { c.0 } + fn from(c: ColourCode) -> u16 { + c.0 + } } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -303,10 +304,23 @@ pub(crate) fn process_geometry<'src>( true, ); }; + let (line_kind, line_delta) = match typ { + LineType::Two | LineType::Five => (LimitKind::Lines, 1), + LineType::Three => (LimitKind::Triangles, 1), + LineType::Four => (LimitKind::Triangles, 2), + _ => unreachable!(), + }; + counters + .add(line_kind, line_delta, limits) + .map_err(|name| limit_error(name, Some(line.span)))?; for point in &points { model.bounds.add(*point); } - let colour = line.tokens.get(1).and_then(|t| t.text.parse::().ok()).unwrap_or(16); + let colour = line + .tokens + .get(1) + .and_then(|t| t.text.parse::().ok()) + .unwrap_or(16); model.geometry.push(GeometryRecord { line_type: u8::from(typ), colour: ColourCode(colour), @@ -314,20 +328,28 @@ pub(crate) fn process_geometry<'src>( }); match typ { LineType::Two => { - model.lines = model.lines.checked_add(1).ok_or(ParseError::Overflow("lines"))?; - counters.add(LimitKind::Lines, 1, limits).map_err(|name| limit_error(name, Some(line.span)))?; + model.lines = model + .lines + .checked_add(1) + .ok_or(ParseError::Overflow("lines"))?; } LineType::Five => { - model.conditional_lines = model.conditional_lines.checked_add(1).ok_or(ParseError::Overflow("conditional_lines"))?; - counters.add(LimitKind::Lines, 1, limits).map_err(|name| limit_error(name, Some(line.span)))?; + model.conditional_lines = model + .conditional_lines + .checked_add(1) + .ok_or(ParseError::Overflow("conditional_lines"))?; } LineType::Three => { - model.triangles = model.triangles.checked_add(1).ok_or(ParseError::Overflow("triangles"))?; - counters.add(LimitKind::Triangles, 1, limits).map_err(|name| limit_error(name, Some(line.span)))?; + model.triangles = model + .triangles + .checked_add(1) + .ok_or(ParseError::Overflow("triangles"))?; } LineType::Four => { - model.quads = model.quads.checked_add(1).ok_or(ParseError::Overflow("quads"))?; - counters.add(LimitKind::Triangles, 2, limits).map_err(|name| limit_error(name, Some(line.span)))?; + model.quads = model + .quads + .checked_add(1) + .ok_or(ParseError::Overflow("quads"))?; } _ => unreachable!(), } diff --git a/crates/polymodel-ldraw-core/src/lib.rs b/crates/polymodel-ldraw-core/src/lib.rs index ac9dbbf..0bad697 100644 --- a/crates/polymodel-ldraw-core/src/lib.rs +++ b/crates/polymodel-ldraw-core/src/lib.rs @@ -105,7 +105,11 @@ mod tests { assert_eq!(first.scene.model_id, "root.ldr"); assert_eq!(first.scene.instance_ids, vec!["root.ldr→child.dat#1"]); assert_eq!( - first.models.iter().map(|model| model.id.as_str()).collect::>(), + first + .models + .iter() + .map(|model| model.id.as_str()) + .collect::>(), vec!["root.ldr", "child.dat"] ); @@ -151,20 +155,15 @@ mod tests { &ledger, ); match fixture.expected.outcome { - polymodel_ldraw_testkit::ExpectedOutcome::Rejected => { - match result { - Err(LdrawError::Diagnostic(_)) => {} - Err(error) => panic!( - "{} rejected with unexpected error: {error}", - fixture.id - ), - Ok(parsed) => assert!( - !parsed.diagnostics.is_empty(), - "{} rejected fixture published without diagnostics", - fixture.id - ), - } - } + polymodel_ldraw_testkit::ExpectedOutcome::Rejected => match result { + Err(LdrawError::Diagnostic(_)) => {} + Err(error) => panic!("{} rejected with unexpected error: {error}", fixture.id), + Ok(parsed) => assert!( + !parsed.diagnostics.is_empty(), + "{} rejected fixture published without diagnostics", + fixture.id + ), + }, polymodel_ldraw_testkit::ExpectedOutcome::Accepted | polymodel_ldraw_testkit::ExpectedOutcome::Preserved | polymodel_ldraw_testkit::ExpectedOutcome::Limited @@ -200,10 +199,7 @@ mod tests { .unwrap_or_else(|| panic!("missing corpus fixture {id}")) } - fn assert_geometry( - geometry: &[GeometryRecord], - expected: &[(u8, u16, &[Point3])], - ) { + fn assert_geometry(geometry: &[GeometryRecord], expected: &[(u8, u16, &[Point3])]) { const EPSILON: f64 = 1e-9; assert_eq!(geometry.len(), expected.len(), "geometry record count"); @@ -211,8 +207,16 @@ mod tests { geometry.iter().zip(expected).enumerate() { assert_eq!(record.line_type, *line_type, "geometry[{index}] line type"); - assert_eq!(record.colour, ColourCode(*colour), "geometry[{index}] colour"); - assert_eq!(record.vertices.len(), vertices.len(), "geometry[{index}] vertex count"); + assert_eq!( + record.colour, + ColourCode(*colour), + "geometry[{index}] colour" + ); + assert_eq!( + record.vertices.len(), + vertices.len(), + "geometry[{index}] vertex count" + ); for (vertex_index, (actual, expected)) in record.vertices.iter().zip(*vertices).enumerate() { @@ -240,7 +244,10 @@ mod tests { assert_eq!(result.scene.triangles, triangles, "triangle count"); assert_eq!(result.scene.quads, quads, "quad count"); assert_eq!(result.scene.lines, lines, "line count"); - assert_eq!(result.scene.conditional_lines, conditional_lines, "conditional line count"); + assert_eq!( + result.scene.conditional_lines, conditional_lines, + "conditional line count" + ); } fn assert_sticker_geometry(result: &ParseResult<'_>) { @@ -251,7 +258,7 @@ mod tests { .expect("sticker sub-model"); assert_geometry( &sticker.geometry, - &[ ( + &[( 4, 16, &[ @@ -260,7 +267,7 @@ mod tests { Point3::new(20.0, -0.25, -30.0), Point3::new(20.0, -0.25, 30.0), ], - ) ], + )], ); for model in &result.models { if model.path != "sticker.ldr" { @@ -366,7 +373,12 @@ mod tests { .iter() .map(|event| event.kind) .collect::>(), - vec![TexmapKind::Start, TexmapKind::Next, TexmapKind::Fallback, TexmapKind::End] + vec![ + TexmapKind::Start, + TexmapKind::Next, + TexmapKind::Fallback, + TexmapKind::End + ] ); let start = &result.semantic.texmap_events[0]; assert_eq!(start.mode, Some(TexmapProjectionMode::Planar)); @@ -438,10 +450,7 @@ mod tests { ( 2, 24, - &[ - Point3::new(0.0, 0.0, 0.0), - Point3::new(10.0, 0.0, 0.0), - ], + &[Point3::new(0.0, 0.0, 0.0), Point3::new(10.0, 0.0, 0.0)], ), ( 3, @@ -475,10 +484,12 @@ mod tests { ], ); assert_eq!(result.models[0].includes.len(), 1); - assert!(result - .diagnostics - .iter() - .any(|diagnostic| diagnostic.code == DiagnosticCode::UnresolvedReference)); + assert!( + result + .diagnostics + .iter() + .any(|diagnostic| diagnostic.code == DiagnosticCode::UnresolvedReference) + ); } #[test] @@ -551,11 +562,28 @@ mod tests { ("graph-instances", "ViperMain.ldr", 9, 250, 0, 0, 0), ("external-thomas", "ViperMain.ldr", 9, 250, 0, 0, 0), ("external-weldr", "main.ldr", 3, 5, 0, 0, 1), - ("external-ldr-tools", "hatching-grounds.ldr", 17, 553, 12, 0, 0), + ( + "external-ldr-tools", + "hatching-grounds.ldr", + 17, + 553, + 12, + 0, + 0, + ), ("mpd-data", "main.ldr", 3, 7, 0, 0, 1), ]; - for (fixture_id, oracle_root, expected_files, expected_includes, expected_steps, expected_tri, expected_quads) in cases { + for ( + fixture_id, + oracle_root, + expected_files, + expected_includes, + expected_steps, + expected_tri, + expected_quads, + ) in cases + { let fixture = corpus_fixture(fixture_id); let result = InProcessRustAdapter::parse_fixture( fixture.id, @@ -566,23 +594,47 @@ mod tests { ) .unwrap_or_else(|e| panic!("{fixture_id} should parse: {e}")); - let target = result.semantic.target_identity.as_ref().map(|s| s.as_ref().to_ascii_lowercase()).unwrap_or_default(); + let target = result + .semantic + .target_identity + .as_ref() + .map(|s| s.as_ref().to_ascii_lowercase()) + .unwrap_or_default(); assert!( - target.contains(&oracle_root.to_ascii_lowercase()) || oracle_root.to_ascii_lowercase().contains(&target) || target.is_empty(), + target.contains(&oracle_root.to_ascii_lowercase()) + || oracle_root.to_ascii_lowercase().contains(&target) + || target.is_empty(), "{fixture_id}: root identity '{target}' should relate to oracle root '{oracle_root}'" ); - assert_eq!(result.files.len(), *expected_files, "{fixture_id}: file count"); + assert_eq!( + result.files.len(), + *expected_files, + "{fixture_id}: file count" + ); let total_includes: usize = result.models.iter().map(|m| m.includes.len()).sum(); - assert!(total_includes >= *expected_includes, "{fixture_id}: expected >={expected_includes} includes, got {total_includes}"); + assert!( + total_includes >= *expected_includes, + "{fixture_id}: expected >={expected_includes} includes, got {total_includes}" + ); if *expected_steps > 0 { - assert!(result.semantic.steps.len() >= *expected_steps, "{fixture_id}: expected >={expected_steps} steps, got {}", result.semantic.steps.len()); + assert!( + result.semantic.steps.len() >= *expected_steps, + "{fixture_id}: expected >={expected_steps} steps, got {}", + result.semantic.steps.len() + ); } - assert_eq!(result.scene.triangles, *expected_tri, "{fixture_id}: triangle count"); - assert_eq!(result.scene.quads, *expected_quads, "{fixture_id}: quad count"); + assert_eq!( + result.scene.triangles, *expected_tri, + "{fixture_id}: triangle count" + ); + assert_eq!( + result.scene.quads, *expected_quads, + "{fixture_id}: quad count" + ); } } } diff --git a/crates/polymodel-ldraw-core/src/model.rs b/crates/polymodel-ldraw-core/src/model.rs index 2c7650c..1bd709e 100644 --- a/crates/polymodel-ldraw-core/src/model.rs +++ b/crates/polymodel-ldraw-core/src/model.rs @@ -1,7 +1,7 @@ use crate::bfc::{BfcFrame, BfcState}; use crate::cache::{CacheKey, NormalizedPath}; -use crate::geom::{deserialize_bounds3, Bounds3, Transform}; use crate::geom::GeometryRecord; +use crate::geom::{Bounds3, Transform, deserialize_bounds3}; use crate::mpd::OwnedVirtualFile; use crate::scanner::{LineType, Token, TokenKind}; use crate::texmap::{TexmapEvent, TexmapState, TextureDescriptor}; @@ -123,7 +123,13 @@ pub(crate) fn parse_colour<'src>(tokens: &[Token<'src>]) -> Option> edge = value_token.and_then(|token| { parse_rgb(token.text) .map(|(r, g, b)| { - ColourEdge::Data(ColourData { r, g, b, alpha: None, luminance: None }) + ColourEdge::Data(ColourData { + r, + g, + b, + alpha: None, + luminance: None, + }) }) .or_else(|| token.text.parse().ok().map(ColourEdge::Code)) }); diff --git a/crates/polymodel-ldraw-core/src/mpd.rs b/crates/polymodel-ldraw-core/src/mpd.rs index 2189a53..380442e 100644 --- a/crates/polymodel-ldraw-core/src/mpd.rs +++ b/crates/polymodel-ldraw-core/src/mpd.rs @@ -1,6 +1,6 @@ use crate::scanner::{LineType, ScannedLine, TokenKind}; use crate::types::{ - Diagnostic, DiagnosticCode, LdrawLimits, ParseError, Severity, Span, DEFAULT_ROOT_NAME, + DEFAULT_ROOT_NAME, Diagnostic, DiagnosticCode, LdrawLimits, ParseError, Severity, Span, }; use serde::{Deserialize, Serialize}; use std::borrow::Cow; diff --git a/crates/polymodel-ldraw-core/src/parser.rs b/crates/polymodel-ldraw-core/src/parser.rs index 55ef770..82b9a26 100644 --- a/crates/polymodel-ldraw-core/src/parser.rs +++ b/crates/polymodel-ldraw-core/src/parser.rs @@ -143,7 +143,28 @@ impl LdrawParser { &mut texture_ids, ); if let Err(error) = result { - if options.profile == ParserProfile::Strict { + let continue_diagnostic = match &error { + ParseError::Diagnostic(diagnostic) => { + options.profile != ParserProfile::Strict + && !matches!( + diagnostic.code, + DiagnosticCode::LineBytesLimit + | DiagnosticCode::FilesLimit + | DiagnosticCode::SemanticArenaReservationLimit + | DiagnosticCode::IncludeDepthLimit + | DiagnosticCode::CommandsLimit + | DiagnosticCode::InstancesLimit + | DiagnosticCode::TrianglesLimit + | DiagnosticCode::LinesLimit + | DiagnosticCode::TexturesLimit + | DiagnosticCode::FetchesLimit + | DiagnosticCode::DiagnosticsLimit + | DiagnosticCode::ResourceBytesLimit + ) + } + _ => false, + }; + if !continue_diagnostic { return Err(error); } } diff --git a/crates/polymodel-ldraw-core/src/scanner.rs b/crates/polymodel-ldraw-core/src/scanner.rs index 35ba6bc..6f89d8b 100644 --- a/crates/polymodel-ldraw-core/src/scanner.rs +++ b/crates/polymodel-ldraw-core/src/scanner.rs @@ -86,8 +86,12 @@ pub enum LineType { impl From for u8 { fn from(lt: LineType) -> u8 { match lt { - LineType::Zero => 0, LineType::One => 1, LineType::Two => 2, - LineType::Three => 3, LineType::Four => 4, LineType::Five => 5, + LineType::Zero => 0, + LineType::One => 1, + LineType::Two => 2, + LineType::Three => 3, + LineType::Four => 4, + LineType::Five => 5, } } } @@ -102,8 +106,12 @@ impl<'de> serde::Deserialize<'de> for LineType { fn deserialize>(d: D) -> Result { let v = u8::deserialize(d)?; match v { - 0 => Ok(LineType::Zero), 1 => Ok(LineType::One), 2 => Ok(LineType::Two), - 3 => Ok(LineType::Three), 4 => Ok(LineType::Four), 5 => Ok(LineType::Five), + 0 => Ok(LineType::Zero), + 1 => Ok(LineType::One), + 2 => Ok(LineType::Two), + 3 => Ok(LineType::Three), + 4 => Ok(LineType::Four), + 5 => Ok(LineType::Five), _ => Err(serde::de::Error::custom(format!("invalid line type: {v}"))), } } diff --git a/crates/polymodel-ldraw-core/src/texmap.rs b/crates/polymodel-ldraw-core/src/texmap.rs index 440cdb3..d266df4 100644 --- a/crates/polymodel-ldraw-core/src/texmap.rs +++ b/crates/polymodel-ldraw-core/src/texmap.rs @@ -71,8 +71,8 @@ pub struct TexmapEvent<'src> { use crate::model::ModelData; use crate::scanner::{ScannedLine, TokenKind}; use crate::types::{ - add_diag, limit_error, Diagnostic, DiagnosticCode, LdrawLimits, LimitCounters, LimitKind, - ParseError, ParserProfile, + Diagnostic, DiagnosticCode, LdrawLimits, LimitCounters, LimitKind, ParseError, ParserProfile, + add_diag, limit_error, }; use std::collections::BTreeSet; diff --git a/crates/polymodel-ldraw-core/src/traversal.rs b/crates/polymodel-ldraw-core/src/traversal.rs index 6dd5c41..d8e44a0 100644 --- a/crates/polymodel-ldraw-core/src/traversal.rs +++ b/crates/polymodel-ldraw-core/src/traversal.rs @@ -2,8 +2,8 @@ use crate::cache::CacheKey; use crate::geom::{Bounds3, Point3, Transform}; use crate::model::{ModelData, ModelSummary, SceneRecord}; use crate::types::{ - add_diag, limit_error, Diagnostic, DiagnosticCode, LimitCounters, LimitKind, ParseError, - ParseOptions, + Diagnostic, DiagnosticCode, LimitCounters, LimitKind, ParseError, ParseOptions, add_diag, + limit_error, }; use std::collections::{HashMap, HashSet, VecDeque}; diff --git a/crates/polymodel-ldraw-testkit/src/schema.rs b/crates/polymodel-ldraw-testkit/src/schema.rs index 38016e1..19f5cc4 100644 --- a/crates/polymodel-ldraw-testkit/src/schema.rs +++ b/crates/polymodel-ldraw-testkit/src/schema.rs @@ -137,8 +137,8 @@ impl<'de> Deserialize<'de> for ExpectedRecord { .map(serde_json::from_value) .transpose() .map_err(serde::de::Error::custom)?; - let canonical_json = serde_json::to_string(&canonical_value) - .map_err(serde::de::Error::custom)?; + let canonical_json = + serde_json::to_string(&canonical_value).map_err(serde::de::Error::custom)?; let canonical_json: &'static str = Box::leak(canonical_json.into_boxed_str()); let canonical = serde_json::from_str(canonical_json).map_err(serde::de::Error::custom)?; let oracle_projection = wire.oracle_projection.or(nested_projection);