Improve and use macro for parsing dictionaries

This commit is contained in:
hal8174 2025-08-11 19:13:50 +02:00
parent d6662da7b9
commit 63a210b8b4
Signed by: hal8174
SSH key fingerprint: SHA256:NN98ZYwnrreQLSOV/g+amY7C3yL/mS1heD7bi5t6PPw
3 changed files with 202 additions and 226 deletions

View file

@ -12,7 +12,7 @@ fn main() -> Result<(), miette::Error> {
let t = parse_pbrt_v4(args.filename)?; let t = parse_pbrt_v4(args.filename)?;
// dbg!(t); dbg!(t);
Ok(()) Ok(())
} }

View file

@ -9,6 +9,7 @@ use ray_tracing_core::{
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use thiserror::Error; use thiserror::Error;
#[macro_use]
mod tokenizer; mod tokenizer;
mod error; mod error;
@ -47,47 +48,6 @@ fn parse_look_at(iter: &mut Tokenizer) -> Result<Statement> {
)) ))
} }
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<Statement> { fn parse_shape(iter: &mut Tokenizer) -> Result<Statement> {
let shape_type = iter.next().ok_or(miette!("unable to get shape type"))??; let shape_type = iter.next().ok_or(miette!("unable to get shape type"))??;
@ -118,242 +78,185 @@ fn parse_shape(iter: &mut Tokenizer) -> Result<Statement> {
)) ))
} }
"\"trianglemesh\"" => { "\"trianglemesh\"" => {
let mut indices = Vec::new(); let t = parse_dict!(iter =>
let mut p = Vec::new(); p, Vec<Pos3>, Vec::new();
let mut n = Vec::new(); n, Vec<Dir3>, Vec::new();
let mut s = Vec::new(); s, Vec<Dir3>, Vec::new();
let mut uv = Vec::new(); uv, Vec<[Float; 2]>, Vec::new();
let mut alpha = ShapeAlpha::None; indices, Vec<usize>, 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()? { if t.p.len() < 3 {
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 {
bail!("At least 3 points required.") 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.") bail!("Indices required for trianglemesh with more than 3 points.")
} }
if indices.len() % 3 != 0 { if t.indices.len() % 3 != 0 {
bail!( bail!(
"number of indices must be divisible by 3. num indices: {}", "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.") 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.") 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.") bail!("Number of uvs not equal to number of positions.")
} }
Ok(Statement::Shape( Ok(Statement::Shape(
ShapeType::TriangleMesh { ShapeType::TriangleMesh {
indices, indices: t.indices,
p, p: t.p,
n, n: t.n,
s, s: t.s,
uv, uv: t.uv,
}, },
alpha, t.alpha,
)) ))
} }
"\"bilinearmesh\"" => { "\"bilinearmesh\"" => {
let mut indices = Vec::new(); let t = parse_dict!(iter =>
let mut p = Vec::new(); p, Vec<Pos3>, Vec::new();
let mut n = Vec::new(); n, Vec<Dir3>, Vec::new();
let mut uv = Vec::new(); uv, Vec<[Float; 2]>, Vec::new();
let mut alpha = ShapeAlpha::None; indices, Vec<usize>, 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()? { if t.p.len() < 4 {
match q.as_str() { bail!("At least 4 points required.")
"\"integer indices\"" => { }
iter.parse_list(&mut indices)?; if t.indices.is_empty() && t.p.len() != 4 {
} bail!("Indices required for trianglemesh with more than 4 points.")
"\"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 p.len() < 4 { if t.indices.len() % 4 != 0 {
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 {
bail!( bail!(
"number of indices must be divisible by 4. num indices: {}", "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.") 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.") bail!("Number of uvs not equal to number of positions.")
} }
Ok(Statement::Shape( Ok(Statement::Shape(
ShapeType::BilinearMesh { indices, p, n, uv }, ShapeType::BilinearMesh {
alpha, indices: t.indices,
p: t.p,
n: t.n,
uv: t.uv,
},
t.alpha,
)) ))
} }
"\"loopsubdiv\"" => { "\"loopsubdiv\"" => {
let mut levels = 3; let t = parse_dict!(iter =>
let mut indices = Vec::new(); levels, u32, 3;
indices, Vec<usize>, Vec::new();
p, Vec<Pos3>, 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(); if t.indices.is_empty() {
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() {
bail!("indices are a required field") bail!("indices are a required field")
} }
if p.is_empty() { if t.p.is_empty() {
bail!("p is a required field") bail!("p is a required field")
} }
Ok(Statement::Shape( Ok(Statement::Shape(
ShapeType::LoopSubDiv { levels, indices, p }, ShapeType::LoopSubDiv {
alpha, levels: t.levels,
indices: t.indices,
p: t.p,
},
t.alpha,
)) ))
} }
"\"disk\"" => { "\"disk\"" => {
let mut height = 0.0; let t = parse_dict!(iter =>
let mut radius = 1.0; height, Float, 0.0;
let mut innerradius = 0.0; radius, Float, 1.0;
let mut phimax = 360.0; innerradius, Float, 0.0;
let mut alpha = ShapeAlpha::None; phimax, Float, 360.0;
alpha, ShapeAlpha, ShapeAlpha::None
while let Some(q) = iter.next_if(|p| p.starts_with('"')).transpose()? { =>
match q.as_str() { height, "\"float height\"", iter.parse_parameter()?;
"\"float height\"" => height = iter.parse_parameter()?, radius, "\"float radius\"", iter.parse_parameter()?;
"\"float radius\"" => radius = iter.parse_parameter()?, innerradius, "\"float innerradius\"", iter.parse_parameter()?;
"\"float innerradius\"" => innerradius = iter.parse_parameter()?, phimax, "\"float phimax\"", iter.parse_parameter()?;
"\"float phimax\"" => phimax = iter.parse_parameter()?, alpha, "\"float alpha\"", ShapeAlpha::Value(iter.parse_parameter()?);
"\"float alpha\"" => { alpha, "\"texture alpha\"", ShapeAlpha::Texture(iter.parse_parameter()?)
alpha = ShapeAlpha::Value(iter.parse_parameter()?); );
}
"\"texture alpha\"" => {
alpha = ShapeAlpha::Texture(iter.parse_parameter()?);
}
_ => {
bail!("unknown argument {}", q)
}
}
}
Ok(Statement::Shape( Ok(Statement::Shape(
ShapeType::Disk { ShapeType::Disk {
height, height: t.height,
radius, radius: t.radius,
innerradius, innerradius: t.innerradius,
phimax, phimax: t.phimax,
}, },
alpha, t.alpha,
)) ))
} }
"\"plymesh\"" => { "\"plymesh\"" => {
let mut filename = String::new(); let t = parse_dict!(iter =>
let mut displacement = None; filename, String, String::new();
let mut edgelength = 1.0; displacement, Option<String>, None;
let mut alpha = ShapeAlpha::None; edgelength, Float, 1.0;
while let Some(q) = iter.next_if(|p| p.starts_with('"')).transpose()? { alpha, ShapeAlpha, ShapeAlpha::None
match q.as_str() { =>
"\"string filename\"" => filename = dbg!(iter.parse_parameter()?), filename, "\"string filename\"", iter.parse_next()?;
"\"texture displacement\"" => displacement = Some(iter.parse_parameter()?), displacement, "\"string displacement\"", Some(iter.parse_next()?);
"\"float edgelength\"" => edgelength = iter.parse_parameter()?, edgelength, "\"float edgelength\"", iter.parse_parameter()?;
"\"float alpha\"" => { alpha, "\"float alpha\"", ShapeAlpha::Value(iter.parse_parameter()?);
alpha = ShapeAlpha::Value(iter.parse_parameter()?); alpha, "\"texture alpha\"", ShapeAlpha::Texture(iter.parse_parameter()?)
} );
"\"texture alpha\"" => {
alpha = ShapeAlpha::Texture(iter.parse_parameter()?);
}
_ => {
bail!("unknown argument {}", q)
}
}
}
Ok(Statement::Shape( Ok(Statement::Shape(
ShapeType::PlyMesh { ShapeType::PlyMesh {
filename, filename: t.filename,
displacement, displacement: t.displacement,
edgelength, edgelength: t.edgelength,
}, },
alpha, t.alpha,
)) ))
} }
_ => Err(miette!("Unknown shape {}", shape_type)), _ => Err(miette!("Unknown shape {}", shape_type)),
@ -586,10 +489,10 @@ enum ShapeType {
p: Vec<Pos3>, p: Vec<Pos3>,
}, },
Disk { Disk {
height: f64, height: Float,
radius: f64, radius: Float,
innerradius: f64, innerradius: Float,
phimax: f64, phimax: Float,
}, },
PlyMesh { PlyMesh {
filename: String, filename: String,

View file

@ -234,12 +234,13 @@ impl Tokenizer {
} }
} }
pub fn parse_list<T>(&mut self, data: &mut Vec<T>) -> Result<()> pub fn parse_list<T>(&mut self) -> Result<Vec<T>>
where where
T: std::str::FromStr, T: std::str::FromStr,
<T as std::str::FromStr>::Err: <T as std::str::FromStr>::Err:
std::marker::Send + std::marker::Sync + std::error::Error + 'static, std::marker::Send + std::marker::Sync + std::error::Error + 'static,
{ {
let mut data = Vec::new();
if !self if !self
.next() .next()
.is_none_or(|p| p.is_ok_and(|p| p.as_str() == "[")) .is_none_or(|p| p.is_ok_and(|p| p.as_str() == "["))
@ -270,16 +271,17 @@ impl Tokenizer {
.into()); .into());
} }
Ok(()) Ok(data)
} }
pub fn parse_list_2<T, P, F>(&mut self, data: &mut Vec<P>, f: F) -> Result<()> pub fn parse_list_2<T, P, F>(&mut self, f: F) -> Result<Vec<P>>
where where
T: std::str::FromStr, T: std::str::FromStr,
<T as std::str::FromStr>::Err: <T as std::str::FromStr>::Err:
std::marker::Send + std::marker::Sync + std::error::Error + 'static, std::marker::Send + std::marker::Sync + std::error::Error + 'static,
F: Fn(T, T) -> P, F: Fn(T, T) -> P,
{ {
let mut data = Vec::new();
if !self if !self
.next() .next()
.is_none_or(|p| p.is_ok_and(|p| p.as_str() == "[")) .is_none_or(|p| p.is_ok_and(|p| p.as_str() == "["))
@ -320,16 +322,17 @@ impl Tokenizer {
.into()); .into());
} }
Ok(()) Ok(data)
} }
pub fn parse_list_3<T, P, F>(&mut self, data: &mut Vec<P>, f: F) -> Result<()> pub fn parse_list_3<T, P, F>(&mut self, f: F) -> Result<Vec<P>>
where where
T: std::str::FromStr, T: std::str::FromStr,
<T as std::str::FromStr>::Err: <T as std::str::FromStr>::Err:
std::marker::Send + std::marker::Sync + std::error::Error + 'static, std::marker::Send + std::marker::Sync + std::error::Error + 'static,
F: Fn(T, T, T) -> P, F: Fn(T, T, T) -> P,
{ {
let mut data = Vec::new();
if !self if !self
.next() .next()
.is_none_or(|p| p.is_ok_and(|p| p.as_str() == "[")) .is_none_or(|p| p.is_ok_and(|p| p.as_str() == "["))
@ -380,8 +383,78 @@ impl Tokenizer {
.into()); .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)] #[derive(Error, Debug, Diagnostic)]