From 63a210b8b477c2a742095f39a1c1c8955a42b78c Mon Sep 17 00:00:00 2001 From: hal8174 Date: Mon, 11 Aug 2025 19:13:50 +0200 Subject: [PATCH] Improve and use macro for parsing dictionaries --- ray-tracing-pbrt-scene/examples/pbrt-test.rs | 2 +- ray-tracing-pbrt-scene/src/lib.rs | 341 +++++++------------ ray-tracing-pbrt-scene/src/tokenizer.rs | 85 ++++- 3 files changed, 202 insertions(+), 226 deletions(-) diff --git a/ray-tracing-pbrt-scene/examples/pbrt-test.rs b/ray-tracing-pbrt-scene/examples/pbrt-test.rs index a49cfc6..d316b9e 100644 --- a/ray-tracing-pbrt-scene/examples/pbrt-test.rs +++ b/ray-tracing-pbrt-scene/examples/pbrt-test.rs @@ -12,7 +12,7 @@ fn main() -> Result<(), miette::Error> { let t = parse_pbrt_v4(args.filename)?; - // dbg!(t); + dbg!(t); Ok(()) } diff --git a/ray-tracing-pbrt-scene/src/lib.rs b/ray-tracing-pbrt-scene/src/lib.rs index 42d8b57..df2c516 100644 --- a/ray-tracing-pbrt-scene/src/lib.rs +++ b/ray-tracing-pbrt-scene/src/lib.rs @@ -9,6 +9,7 @@ use ray_tracing_core::{ use std::path::{Path, PathBuf}; use thiserror::Error; +#[macro_use] mod tokenizer; mod error; @@ -47,47 +48,6 @@ fn parse_look_at(iter: &mut Tokenizer) -> Result { )) } -macro_rules! parse_dict { - ($tokenizer:expr => $($name_decl:ident, $type:ty, $default:expr);+ => $($name_parsing:ident, $expr:expr, $parsing:expr);+ - ) => { - { - $( - let mut $name_decl = None; - )+ - - while let Some(p) = $tokenizer.next_if(|p| p.starts_with('"')).transpose()? { - match p.as_str() { - $( - $expr => { - if $name_parsing.is_none() { - $name_parsing = Some($parsing); - } else { - return Err(miette!("dfs")) - } - } - )+ - _ => {todo!()} - } - } - - #[derive(Debug)] - struct Dict { - $( - $name_decl: $type, - )+ - } - - $( - let $name_decl = $name_decl.unwrap_or_else(|| $default); - )* - - Dict { - $($name_decl,)* - } - } - }; -} - fn parse_shape(iter: &mut Tokenizer) -> Result { let shape_type = iter.next().ok_or(miette!("unable to get shape type"))??; @@ -118,242 +78,185 @@ fn parse_shape(iter: &mut Tokenizer) -> Result { )) } "\"trianglemesh\"" => { - let mut indices = Vec::new(); - let mut p = Vec::new(); - let mut n = Vec::new(); - let mut s = Vec::new(); - let mut uv = Vec::new(); - let mut alpha = ShapeAlpha::None; + let t = parse_dict!(iter => + p, Vec, Vec::new(); + n, Vec, Vec::new(); + s, Vec, Vec::new(); + uv, Vec<[Float; 2]>, Vec::new(); + indices, Vec, Vec::new(); + alpha, ShapeAlpha, ShapeAlpha::None + => + p, "\"point3 P\"", iter.parse_list_3(Pos3::new)?; + n, "\"normal N\"", iter.parse_list_3(Dir3::new)?; + s, "\"normal S\"", iter.parse_list_3(Dir3::new)?; + uv, "\"point2 uv\"", iter.parse_list_2(|u, v| [u, v])?; + indices, "\"interger indices\"", iter.parse_list()?; + alpha, "\"float alpha\"", ShapeAlpha::Value(iter.parse_parameter()?); + alpha, "\"texture alpha\"", ShapeAlpha::Texture(iter.parse_parameter()?) + ); - while let Some(q) = iter.next_if(|p| p.starts_with('"')).transpose()? { - match q.as_str() { - "\"integer indices\"" => { - iter.parse_list(&mut indices)?; - } - "\"point3 P\"" => { - iter.parse_list_3(&mut p, Pos3::new)?; - } - "\"normal N\"" => { - iter.parse_list_3(&mut n, Dir3::new)?; - } - "\"vector S\"" => { - iter.parse_list_3(&mut s, Dir3::new)?; - } - "\"point2 uv\"" => { - iter.parse_list_2(&mut uv, |u, v| [u, v])?; - } - "\"float alpha\"" => { - alpha = ShapeAlpha::Value(iter.parse_parameter()?); - } - "\"texture alpha\"" => { - alpha = ShapeAlpha::Texture(iter.parse_parameter()?); - } - _ => { - bail!("unknown argument {}", q) - } - } - } - if p.len() < 3 { + if t.p.len() < 3 { bail!("At least 3 points required.") } - if indices.is_empty() && p.len() != 3 { + if t.indices.is_empty() && t.p.len() != 3 { bail!("Indices required for trianglemesh with more than 3 points.") } - if indices.len() % 3 != 0 { + if t.indices.len() % 3 != 0 { bail!( "number of indices must be divisible by 3. num indices: {}", - indices.len() + t.indices.len() ) } - if !n.is_empty() && n.len() != p.len() { + if !t.n.is_empty() && t.n.len() != t.p.len() { bail!("Number of normals not equal to number of positions.") } - if !s.is_empty() && s.len() != p.len() { + if !t.s.is_empty() && t.s.len() != t.p.len() { bail!("Number of tangents not equal to number of positions.") } - if !uv.is_empty() && uv.len() != p.len() { + if !t.uv.is_empty() && t.uv.len() != t.p.len() { bail!("Number of uvs not equal to number of positions.") } + Ok(Statement::Shape( ShapeType::TriangleMesh { - indices, - p, - n, - s, - uv, + indices: t.indices, + p: t.p, + n: t.n, + s: t.s, + uv: t.uv, }, - alpha, + t.alpha, )) } "\"bilinearmesh\"" => { - let mut indices = Vec::new(); - let mut p = Vec::new(); - let mut n = Vec::new(); - let mut uv = Vec::new(); - let mut alpha = ShapeAlpha::None; + let t = parse_dict!(iter => + p, Vec, Vec::new(); + n, Vec, Vec::new(); + uv, Vec<[Float; 2]>, Vec::new(); + indices, Vec, Vec::new(); + alpha, ShapeAlpha, ShapeAlpha::None + => + p, "\"point3 P\"", iter.parse_list_3(Pos3::new)?; + n, "\"normal N\"", iter.parse_list_3(Dir3::new)?; + uv, "\"point2 uv\"", iter.parse_list_2(|u, v| [u, v])?; + indices, "\"interger indices\"", iter.parse_list()?; + alpha, "\"float alpha\"", ShapeAlpha::Value(iter.parse_parameter()?); + alpha, "\"texture alpha\"", ShapeAlpha::Texture(iter.parse_parameter()?) + ); - while let Some(q) = iter.next_if(|p| p.starts_with('"')).transpose()? { - match q.as_str() { - "\"integer indices\"" => { - iter.parse_list(&mut indices)?; - } - "\"point3 P\"" => { - iter.parse_list_3(&mut p, Pos3::new)?; - } - "\"point2 uv\"" => { - iter.parse_list_2(&mut uv, |u, v| [u, v])?; - } - "\"vector N\"" => { - iter.parse_list_3(&mut n, Dir3::new)?; - } - "\"float alpha\"" => { - alpha = ShapeAlpha::Value(iter.parse_parameter()?); - } - "\"texture alpha\"" => { - alpha = ShapeAlpha::Texture(iter.parse_parameter()?); - } - _ => { - bail!("unknown argument {}", q) - } - } + if t.p.len() < 4 { + bail!("At least 4 points required.") + } + if t.indices.is_empty() && t.p.len() != 4 { + bail!("Indices required for trianglemesh with more than 4 points.") } - if p.len() < 4 { - bail!("At least 3 points required.") - } - if indices.is_empty() && p.len() != 4 { - bail!("Indices required for trianglemesh with more than 3 points.") - } - - if indices.len() % 4 != 0 { + if t.indices.len() % 4 != 0 { bail!( "number of indices must be divisible by 4. num indices: {}", - indices.len() + t.indices.len() ) } - if !n.is_empty() && n.len() != p.len() { + if !t.n.is_empty() && t.n.len() != t.p.len() { bail!("Number of normals not equal to number of positions.") } - if !uv.is_empty() && uv.len() != p.len() { + if !t.uv.is_empty() && t.uv.len() != t.p.len() { bail!("Number of uvs not equal to number of positions.") } + Ok(Statement::Shape( - ShapeType::BilinearMesh { indices, p, n, uv }, - alpha, + ShapeType::BilinearMesh { + indices: t.indices, + p: t.p, + n: t.n, + uv: t.uv, + }, + t.alpha, )) } - "\"loopsubdiv\"" => { - let mut levels = 3; - let mut indices = Vec::new(); + let t = parse_dict!(iter => + levels, u32, 3; + indices, Vec, Vec::new(); + p, Vec, Vec::new(); + alpha, ShapeAlpha, ShapeAlpha::None + => + levels, "\"float levels\"", iter.parse_parameter()?; + indices, "\"interger indices\"", iter.parse_list()?; + p, "\"point3 P\"", iter.parse_list_3(Pos3::new)?; + alpha, "\"float alpha\"", ShapeAlpha::Value(iter.parse_parameter()?); + alpha, "\"texture alpha\"", ShapeAlpha::Texture(iter.parse_parameter()?) + ); - let mut p = Vec::new(); - let mut alpha = ShapeAlpha::None; - - while let Some(q) = iter.next_if(|p| p.starts_with('"')).transpose()? { - match q.as_str() { - "\"point3 P\"" => { - iter.parse_list_3(&mut p, Pos3::new)?; - } - "\"integer indices\"" => { - iter.parse_list(&mut indices)?; - } - "\"integer levels\"" => { - levels = iter.parse_parameter()?; - } - "\"float alpha\"" => { - alpha = ShapeAlpha::Value(iter.parse_parameter()?); - } - "\"texture alpha\"" => { - alpha = ShapeAlpha::Texture(iter.parse_parameter()?); - } - _ => { - bail!("unknown argument {}", q) - } - } - } - - if indices.is_empty() { + if t.indices.is_empty() { bail!("indices are a required field") } - if p.is_empty() { + if t.p.is_empty() { bail!("p is a required field") } Ok(Statement::Shape( - ShapeType::LoopSubDiv { levels, indices, p }, - alpha, + ShapeType::LoopSubDiv { + levels: t.levels, + indices: t.indices, + p: t.p, + }, + t.alpha, )) } "\"disk\"" => { - let mut height = 0.0; - let mut radius = 1.0; - let mut innerradius = 0.0; - let mut phimax = 360.0; - let mut alpha = ShapeAlpha::None; - - while let Some(q) = iter.next_if(|p| p.starts_with('"')).transpose()? { - match q.as_str() { - "\"float height\"" => height = iter.parse_parameter()?, - "\"float radius\"" => radius = iter.parse_parameter()?, - "\"float innerradius\"" => innerradius = iter.parse_parameter()?, - "\"float phimax\"" => phimax = iter.parse_parameter()?, - "\"float alpha\"" => { - alpha = ShapeAlpha::Value(iter.parse_parameter()?); - } - "\"texture alpha\"" => { - alpha = ShapeAlpha::Texture(iter.parse_parameter()?); - } - _ => { - bail!("unknown argument {}", q) - } - } - } + let t = parse_dict!(iter => + height, Float, 0.0; + radius, Float, 1.0; + innerradius, Float, 0.0; + phimax, Float, 360.0; + alpha, ShapeAlpha, ShapeAlpha::None + => + height, "\"float height\"", iter.parse_parameter()?; + radius, "\"float radius\"", iter.parse_parameter()?; + innerradius, "\"float innerradius\"", iter.parse_parameter()?; + phimax, "\"float phimax\"", iter.parse_parameter()?; + alpha, "\"float alpha\"", ShapeAlpha::Value(iter.parse_parameter()?); + alpha, "\"texture alpha\"", ShapeAlpha::Texture(iter.parse_parameter()?) + ); Ok(Statement::Shape( ShapeType::Disk { - height, - radius, - innerradius, - phimax, + height: t.height, + radius: t.radius, + innerradius: t.innerradius, + phimax: t.phimax, }, - alpha, + t.alpha, )) } "\"plymesh\"" => { - let mut filename = String::new(); - let mut displacement = None; - let mut edgelength = 1.0; - let mut alpha = ShapeAlpha::None; - while let Some(q) = iter.next_if(|p| p.starts_with('"')).transpose()? { - match q.as_str() { - "\"string filename\"" => filename = dbg!(iter.parse_parameter()?), - "\"texture displacement\"" => displacement = Some(iter.parse_parameter()?), - "\"float edgelength\"" => edgelength = iter.parse_parameter()?, - "\"float alpha\"" => { - alpha = ShapeAlpha::Value(iter.parse_parameter()?); - } - "\"texture alpha\"" => { - alpha = ShapeAlpha::Texture(iter.parse_parameter()?); - } - _ => { - bail!("unknown argument {}", q) - } - } - } + let t = parse_dict!(iter => + filename, String, String::new(); + displacement, Option, None; + edgelength, Float, 1.0; + alpha, ShapeAlpha, ShapeAlpha::None + => + filename, "\"string filename\"", iter.parse_next()?; + displacement, "\"string displacement\"", Some(iter.parse_next()?); + edgelength, "\"float edgelength\"", iter.parse_parameter()?; + alpha, "\"float alpha\"", ShapeAlpha::Value(iter.parse_parameter()?); + alpha, "\"texture alpha\"", ShapeAlpha::Texture(iter.parse_parameter()?) + ); + Ok(Statement::Shape( ShapeType::PlyMesh { - filename, - displacement, - edgelength, + filename: t.filename, + displacement: t.displacement, + edgelength: t.edgelength, }, - alpha, + t.alpha, )) } _ => Err(miette!("Unknown shape {}", shape_type)), @@ -586,10 +489,10 @@ enum ShapeType { p: Vec, }, Disk { - height: f64, - radius: f64, - innerradius: f64, - phimax: f64, + height: Float, + radius: Float, + innerradius: Float, + phimax: Float, }, PlyMesh { filename: String, diff --git a/ray-tracing-pbrt-scene/src/tokenizer.rs b/ray-tracing-pbrt-scene/src/tokenizer.rs index 1cbbc7c..f586e72 100644 --- a/ray-tracing-pbrt-scene/src/tokenizer.rs +++ b/ray-tracing-pbrt-scene/src/tokenizer.rs @@ -234,12 +234,13 @@ impl Tokenizer { } } - pub fn parse_list(&mut self, data: &mut Vec) -> Result<()> + pub fn parse_list(&mut self) -> Result> where T: std::str::FromStr, ::Err: std::marker::Send + std::marker::Sync + std::error::Error + 'static, { + let mut data = Vec::new(); if !self .next() .is_none_or(|p| p.is_ok_and(|p| p.as_str() == "[")) @@ -270,16 +271,17 @@ impl Tokenizer { .into()); } - Ok(()) + Ok(data) } - pub fn parse_list_2(&mut self, data: &mut Vec

, f: F) -> Result<()> + pub fn parse_list_2(&mut self, f: F) -> Result> where T: std::str::FromStr, ::Err: std::marker::Send + std::marker::Sync + std::error::Error + 'static, F: Fn(T, T) -> P, { + let mut data = Vec::new(); if !self .next() .is_none_or(|p| p.is_ok_and(|p| p.as_str() == "[")) @@ -320,16 +322,17 @@ impl Tokenizer { .into()); } - Ok(()) + Ok(data) } - pub fn parse_list_3(&mut self, data: &mut Vec

, f: F) -> Result<()> + pub fn parse_list_3(&mut self, f: F) -> Result> where T: std::str::FromStr, ::Err: std::marker::Send + std::marker::Sync + std::error::Error + 'static, F: Fn(T, T, T) -> P, { + let mut data = Vec::new(); if !self .next() .is_none_or(|p| p.is_ok_and(|p| p.as_str() == "[")) @@ -380,8 +383,78 @@ impl Tokenizer { .into()); } - Ok(()) + Ok(data) } + + pub fn get_src(&self) -> SourceFile { + SourceFile { + path: self.path.clone(), + } + } +} + +macro_rules! parse_dict { + ($tokenizer:expr => $($name_decl:ident, $type:ty, $default:expr);+ => $($name_parsing:ident, $expr:expr, $parsing:expr);+ + ) => { + { + $( + let mut $name_decl = None; + )+ + + while let Some(p) = $tokenizer.next_if(|p| p.starts_with('"')).transpose()? { + match p.as_str() { + $( + $expr => { + if $name_parsing.is_none() { + $name_parsing = Some($parsing); + } else { + return Err($crate::tokenizer::DuplicateDictEntryError {bad_bit: $tokenizer.last_span(), src: $tokenizer.get_src()}.into()) + } + } + )+ + _ => { + return Err($crate::tokenizer::UnknownDictEntryError {bad_bit: $tokenizer.last_span(), src: $tokenizer.get_src()}.into()) + } + } + } + + #[derive(Debug)] + struct Dict { + $( + $name_decl: $type, + )+ + } + + $( + let $name_decl = $name_decl.unwrap_or_else(|| $default); + )* + + Dict { + $($name_decl,)* + } + } + }; +} + +#[derive(Error, Debug, Diagnostic)] +#[error("Duplicate dict entry error")] +#[diagnostic(help("multiple dict entries with the same key"))] +pub struct DuplicateDictEntryError { + #[source_code] + pub src: SourceFile, + + #[label("Here")] + pub bad_bit: SourceSpan, +} + +#[derive(Error, Debug, Diagnostic)] +#[error("Unknown dict entry error")] +pub struct UnknownDictEntryError { + #[source_code] + pub src: SourceFile, + + #[label("Here")] + pub bad_bit: SourceSpan, } #[derive(Error, Debug, Diagnostic)]