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)?;
// dbg!(t);
dbg!(t);
Ok(())
}

View file

@ -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<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> {
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\"" => {
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<Pos3>, Vec::new();
n, Vec<Dir3>, Vec::new();
s, Vec<Dir3>, Vec::new();
uv, Vec<[Float; 2]>, Vec::new();
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()? {
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<Pos3>, Vec::new();
n, Vec<Dir3>, Vec::new();
uv, Vec<[Float; 2]>, Vec::new();
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()? {
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<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();
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<String>, 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<Pos3>,
},
Disk {
height: f64,
radius: f64,
innerradius: f64,
phimax: f64,
height: Float,
radius: Float,
innerradius: Float,
phimax: Float,
},
PlyMesh {
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
T: std::str::FromStr,
<T as 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<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
T: std::str::FromStr,
<T as 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<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
T: std::str::FromStr,
<T as 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)]