From de770cd465572ed8a135b679239e0e857767d242 Mon Sep 17 00:00:00 2001 From: Maksym Pavlenko Date: Tue, 7 Apr 2026 12:55:08 -0700 Subject: [PATCH 1/7] Add one_or_list parser helper --- src/usda/parser.rs | 88 +++++++++++----------------------------------- 1 file changed, 21 insertions(+), 67 deletions(-) diff --git a/src/usda/parser.rs b/src/usda/parser.rs index 57dd6bd..acbfb62 100644 --- a/src/usda/parser.rs +++ b/src/usda/parser.rs @@ -151,6 +151,20 @@ impl<'a> Parser<'a> { .ok_or_else(|| anyhow!("Unexpected token {token:?} (want String)")) } + /// Parses a single item or a bracketed array of items. + fn one_or_list(&mut self, mut parse: impl FnMut(&mut Self) -> Result) -> Result> { + if self.is_next(Token::Punctuation('[')) { + let mut out = Vec::new(); + self.parse_array_fn(|this| { + out.push(parse(this)?); + Ok(()) + })?; + Ok(out) + } else { + Ok(vec![parse(self)?]) + } + } + /// Parse tokens to specs. /// Walks the entire token stream, seeding the pseudo root and recursing through every prim. pub fn parse(&mut self) -> Result> { @@ -519,16 +533,7 @@ impl<'a> Parser<'a> { } /// Parses a connection target list into USD paths. fn parse_connection_targets(&mut self) -> Result> { - if self.is_next(Token::Punctuation('[')) { - let mut paths = Vec::new(); - self.parse_array_fn(|this| { - paths.push(this.parse_path_reference().context("Connection path expected")?); - Ok(()) - })?; - Ok(paths) - } else { - Ok(vec![self.parse_path_reference()?]) - } + self.one_or_list(|this| this.parse_path_reference().context("Connection path expected")) } /// Parses a single `<...>` path reference token into an `sdf::Path`. @@ -816,28 +821,28 @@ impl<'a> Parser<'a> { spec.add(FieldKey::Active, sdf::Value::Bool(value)); } "apiSchemas" => { - let values = self.parse_token_list().context("Unable to parse apiSchemas list")?; + let values = self.one_or_list(|this| this.parse_token::()).context("Unable to parse apiSchemas list")?; let list_op = self .apply_list_op(list_op, values) .context("Unable to build apiSchemas listOp")?; spec.add("apiSchemas", sdf::Value::TokenListOp(list_op)); } n if n == FieldKey::References.as_str() => { - let references = self.parse_reference_list().context("Unable to parse references")?; + let references = self.one_or_list(Self::parse_reference).context("Unable to parse references")?; let list_op = self .apply_list_op(list_op, references) .context("Unable to build references listOp")?; spec.add(FieldKey::References, sdf::Value::ReferenceListOp(list_op)); } n if n == FieldKey::Payload.as_str() => { - let payloads = self.parse_payload_list().context("Unable to parse payloads")?; + let payloads = self.one_or_list(Self::parse_payload).context("Unable to parse payloads")?; let list_op = self .apply_list_op(list_op, payloads) .context("Unable to build payload listOp")?; spec.add(FieldKey::Payload, sdf::Value::PayloadListOp(list_op)); } n if n == FieldKey::InheritPaths.as_str() => { - let paths = self.parse_path_ref_list()?; + let paths = self.one_or_list(Self::parse_path_ref)?; let list_op = self .apply_list_op(list_op, paths) .context("Unable to build inherits listOp")?; @@ -877,14 +882,14 @@ impl<'a> Parser<'a> { } } n if n == FieldKey::VariantSetNames.as_str() => { - let values = self.parse_token_list().context("Unable to parse variantSets")?; + let values = self.one_or_list(|this| this.parse_token::()).context("Unable to parse variantSets")?; let list_op = self .apply_list_op(list_op, values) .context("Unable to build variantSets listOp")?; spec.add(FieldKey::VariantSetNames, sdf::Value::TokenListOp(list_op)); } n if n == FieldKey::Specializes.as_str() => { - let paths = self.parse_path_ref_list()?; + let paths = self.one_or_list(Self::parse_path_ref)?; let list_op = self .apply_list_op(list_op, paths) .context("Unable to build specializes listOp")?; @@ -959,20 +964,6 @@ impl<'a> Parser<'a> { Ok(()) } - /// Parse a list-op friendly sequence of references. - fn parse_reference_list(&mut self) -> Result> { - if self.is_next(Token::Punctuation('[')) { - let mut out = Vec::new(); - self.parse_array_fn(|this| { - out.push(this.parse_reference()?); - Ok(()) - })?; - Ok(out) - } else { - Ok(vec![self.parse_reference()?]) - } - } - /// Parse one payload entry, including optional target prim path and layer offset. fn parse_payload(&mut self) -> Result { let mut payload = sdf::Payload { @@ -1007,20 +998,6 @@ impl<'a> Parser<'a> { Ok(payload) } - /// Parse a list-op friendly sequence of payloads. - fn parse_payload_list(&mut self) -> Result> { - if self.is_next(Token::Punctuation('[')) { - let mut out = Vec::new(); - self.parse_array_fn(|this| { - out.push(this.parse_payload()?); - Ok(()) - })?; - Ok(out) - } else { - Ok(vec![self.parse_payload()?]) - } - } - /// Parse a single `` reference. fn parse_path_ref(&mut self) -> Result { let token = self.fetch_next()?; @@ -1031,29 +1008,6 @@ impl<'a> Parser<'a> { sdf::Path::new(path_str) } - /// Parse a single `` or an array of `[, ...]`. - fn parse_path_ref_list(&mut self) -> Result> { - if self.is_next(Token::Punctuation('[')) { - let mut paths = Vec::new(); - self.parse_array_fn(|this| { - paths.push(this.parse_path_ref()?); - Ok(()) - })?; - Ok(paths) - } else { - Ok(vec![self.parse_path_ref()?]) - } - } - - fn parse_token_list(&mut self) -> Result> { - if self.is_next(Token::Punctuation('[')) { - self.parse_array() - } else { - let value = self.parse_token::()?; - Ok(vec![value]) - } - } - fn apply_list_op( &mut self, op: Option>, From f0cb5d99376c50ae7b867246e7923b977cf42f41 Mon Sep 17 00:00:00 2001 From: Maksym Pavlenko Date: Tue, 7 Apr 2026 12:57:27 -0700 Subject: [PATCH 2/7] Add try_list_op and expect_identifier parser helpers --- src/usda/parser.rs | 60 ++++++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/src/usda/parser.rs b/src/usda/parser.rs index acbfb62..dd93416 100644 --- a/src/usda/parser.rs +++ b/src/usda/parser.rs @@ -151,6 +151,24 @@ impl<'a> Parser<'a> { .ok_or_else(|| anyhow!("Unexpected token {token:?} (want String)")) } + /// Consumes and returns an identifier token, or errors. + fn expect_identifier(&mut self) -> Result<&'a str> { + match self.fetch_next()? { + Token::Identifier(s) | Token::NamespacedIdentifier(s) => Ok(s), + other => bail!("expected identifier, got {other:?}"), + } + } + + /// Tries to consume a list-op keyword (`add`, `append`, `prepend`, `delete`, `reorder`). + fn try_list_op(&mut self) -> Option> { + match self.peek_next() { + Some(Ok(Token::Add | Token::Append | Token::Prepend | Token::Delete | Token::Reorder)) => { + self.fetch_next().ok() + } + _ => None, + } + } + /// Parses a single item or a bracketed array of items. fn one_or_list(&mut self, mut parse: impl FnMut(&mut Self) -> Result) -> Result> { if self.is_next(Token::Punctuation('[')) { @@ -451,11 +469,7 @@ impl<'a> Parser<'a> { self.fetch_next()?; } - let type_token = self.fetch_next()?; - let type_name = match type_token { - Token::Identifier(s) | Token::NamespacedIdentifier(s) => s, - other => bail!("Unexpected token type for attribute type, expected Identifier, got {other:?}"), - }; + let type_name = self.expect_identifier().context("attribute type expected")?; let data_type = Self::parse_data_type(type_name)?; let name_token = self.fetch_next()?; @@ -474,12 +488,7 @@ impl<'a> Parser<'a> { if name.contains(".connect") { if self.is_next(Token::Punctuation('=')) { self.fetch_next()?; - let list_op = match self.peek_next() { - Some(Ok(Token::Add | Token::Append | Token::Prepend | Token::Delete | Token::Reorder)) => { - Some(self.fetch_next()?) - } - _ => None, - }; + let list_op = self.try_list_op(); let targets = self .parse_connection_targets() .context("Unable to parse connection targets")?; @@ -705,11 +714,7 @@ impl<'a> Parser<'a> { properties: &mut Vec, data: &mut HashMap, ) -> Result<()> { - let name_token = self.fetch_next()?; - let name = match name_token { - Token::Identifier(s) | Token::NamespacedIdentifier(s) => s, - other => bail!("Unexpected token in relationship declaration: {other:?}"), - }; + let name = self.expect_identifier().context("relationship name expected")?; let mut spec = sdf::Spec::new(sdf::SpecType::Relationship); @@ -735,12 +740,7 @@ impl<'a> Parser<'a> { } self.ensure_pun('=')?; - let list_op = match self.peek_next() { - Some(Ok(Token::Add | Token::Append | Token::Prepend | Token::Delete | Token::Reorder)) => { - Some(self.fetch_next()?) - } - _ => None, - }; + let list_op = self.try_list_op(); let targets = self .parse_connection_targets() .context("Unable to parse relationship targets")?; @@ -821,21 +821,27 @@ impl<'a> Parser<'a> { spec.add(FieldKey::Active, sdf::Value::Bool(value)); } "apiSchemas" => { - let values = self.one_or_list(|this| this.parse_token::()).context("Unable to parse apiSchemas list")?; + let values = self + .one_or_list(|this| this.parse_token::()) + .context("Unable to parse apiSchemas list")?; let list_op = self .apply_list_op(list_op, values) .context("Unable to build apiSchemas listOp")?; spec.add("apiSchemas", sdf::Value::TokenListOp(list_op)); } n if n == FieldKey::References.as_str() => { - let references = self.one_or_list(Self::parse_reference).context("Unable to parse references")?; + let references = self + .one_or_list(Self::parse_reference) + .context("Unable to parse references")?; let list_op = self .apply_list_op(list_op, references) .context("Unable to build references listOp")?; spec.add(FieldKey::References, sdf::Value::ReferenceListOp(list_op)); } n if n == FieldKey::Payload.as_str() => { - let payloads = self.one_or_list(Self::parse_payload).context("Unable to parse payloads")?; + let payloads = self + .one_or_list(Self::parse_payload) + .context("Unable to parse payloads")?; let list_op = self .apply_list_op(list_op, payloads) .context("Unable to build payload listOp")?; @@ -882,7 +888,9 @@ impl<'a> Parser<'a> { } } n if n == FieldKey::VariantSetNames.as_str() => { - let values = self.one_or_list(|this| this.parse_token::()).context("Unable to parse variantSets")?; + let values = self + .one_or_list(|this| this.parse_token::()) + .context("Unable to parse variantSets")?; let list_op = self .apply_list_op(list_op, values) .context("Unable to build variantSets listOp")?; From ba59a6b4e47e57dbf30487e3977bdc79f9d7a3ab Mon Sep 17 00:00:00 2001 From: Maksym Pavlenko Date: Tue, 7 Apr 2026 13:03:43 -0700 Subject: [PATCH 3/7] Add parse_block helper for delimited blocks --- src/usda/parser.rs | 117 +++++++++++++++++---------------------------- 1 file changed, 43 insertions(+), 74 deletions(-) diff --git a/src/usda/parser.rs b/src/usda/parser.rs index dd93416..6cef161 100644 --- a/src/usda/parser.rs +++ b/src/usda/parser.rs @@ -173,7 +173,7 @@ impl<'a> Parser<'a> { fn one_or_list(&mut self, mut parse: impl FnMut(&mut Self) -> Result) -> Result> { if self.is_next(Token::Punctuation('[')) { let mut out = Vec::new(); - self.parse_array_fn(|this| { + self.parse_block('[', ']',|this| { out.push(parse(this)?); Ok(()) })?; @@ -557,15 +557,8 @@ impl<'a> Parser<'a> { /// Parse the metadata block attached to an attribute and stash entries on the spec. fn parse_property_metadata(&mut self, spec: &mut sdf::Spec) -> Result<()> { - self.ensure_pun('(')?; - - loop { - if self.is_next(Token::Punctuation(')')) { - self.fetch_next()?; - break; - } - - let name_token = self.fetch_next()?; + self.parse_block('(', ')', |this| { + let name_token = this.fetch_next()?; let name = match name_token { Token::Identifier(s) | Token::NamespacedIdentifier(s) => s.to_owned(), Token::CustomData => "customData".to_owned(), @@ -580,26 +573,23 @@ impl<'a> Parser<'a> { } }; - self.ensure_pun('=')?; - let value = self + this.ensure_pun('=')?; + let value = this .parse_property_metadata_value() .with_context(|| format!("Unable to parse attribute metadata value for {name}"))?; spec.fields.insert(name, value); - - if self.is_next(Token::Punctuation(',')) { - self.fetch_next()?; - } - } + Ok(()) + })?; Ok(()) } /// Parse a single attribute metadata value (scalar or array) from within a metadata block. fn parse_property_metadata_value(&mut self) -> Result { - // Handle array case first by peeking, so parse_array_fn can consume the '[' + // Handle array case first by peeking, so parse_block can consume the '[' if self.is_next(Token::Punctuation('[')) { let mut values = Vec::new(); - self.parse_array_fn(|this| { + self.parse_block('[', ']',|this| { let entry = this.fetch_next()?; let value = match entry { Token::String(v) => v.to_owned(), @@ -641,38 +631,20 @@ impl<'a> Parser<'a> { /// Parse a dictionary value from `{` to `}`. fn parse_dictionary(&mut self) -> Result { - self.ensure_pun('{').context("Dictionary must start with {")?; - let mut dict = HashMap::new(); - loop { - // Check for closing brace - if self.is_next(Token::Punctuation('}')) { - self.fetch_next()?; - break; - } - - // Parse the type (optional) or key - let first_token = self.fetch_next()?; + self.parse_block('{', '}', |this| { + // Parse the type (optional) or key. + let first_token = this.fetch_next()?; - // Check if this is a type declaration (e.g., "string", "dictionary", "double3") - let (_type_hint, key_token) = match first_token { - Token::Identifier(name) if Self::is_type_hint_name(name) => { - // This is a type declaration, next token is the key - let key = self.fetch_next()?; - (Some(first_token), key) - } - Token::Dictionary => { - // This is a type declaration, next token is the key - let key = self.fetch_next()?; - (Some(first_token), key) - } + let (type_hint, key_token) = match first_token { + Token::Identifier(name) if Self::is_type_hint_name(name) => (Some(first_token), this.fetch_next()?), + Token::Dictionary => (Some(first_token), this.fetch_next()?), _ => (None, first_token), }; let key = match key_token { Token::Identifier(s) | Token::NamespacedIdentifier(s) | Token::String(s) => s.to_owned(), - // Allow keywords as dictionary keys by converting them to strings other => { if let Some(lexeme) = keyword_lexeme(&other) { lexeme.to_owned() @@ -682,28 +654,22 @@ impl<'a> Parser<'a> { } }; - self.ensure_pun('=')?; + this.ensure_pun('=')?; - // Parse the value recursively - let value = if let Some(type_hint_token) = _type_hint { + let value = if let Some(type_hint_token) = type_hint { let ty = match type_hint_token { Token::Dictionary => Type::Dictionary, Token::Identifier(type_name) => Self::parse_data_type(type_name) .with_context(|| format!("Unable to parse dictionary value type {type_name}"))?, other => bail!("Unsupported dictionary type hint: {other:?}"), }; - self.parse_value(ty)? + this.parse_value(ty)? } else { - self.parse_property_metadata_value()? + this.parse_property_metadata_value()? }; dict.insert(key, value); - - // Handle optional trailing comma or newline - if self.is_next(Token::Punctuation('}')) { - self.fetch_next()?; - break; - } - } + Ok(()) + })?; Ok(sdf::Value::Dictionary(dict)) } @@ -1266,7 +1232,7 @@ impl<'a> Parser<'a> { /// Parse an array of booleans, reusing the permissive literal parsing rules. fn parse_bool_array(&mut self) -> Result> { let mut out = Vec::new(); - self.parse_array_fn(|this| { + self.parse_block('[', ']',|this| { out.push(this.parse_bool()?); Ok(()) })?; @@ -1283,7 +1249,7 @@ impl<'a> Parser<'a> { fn parse_asset_path_array(&mut self) -> Result> { let mut result = Vec::new(); - self.parse_array_fn(|this| { + self.parse_block('[', ']',|this| { result.push(this.parse_asset_path()?); Ok(()) })?; @@ -1295,7 +1261,7 @@ impl<'a> Parser<'a> { let mut sublayers = Vec::new(); let mut sublayer_offsets = Vec::new(); - self.parse_array_fn(|this| { + self.parse_block('[', ']',|this| { let asset_path = this .fetch_next()? .try_as_asset_ref() @@ -1345,23 +1311,26 @@ impl<'a> Parser<'a> { } /// Generic array parser that delegates element parsing while handling delimiters. - fn parse_array_fn(&mut self, mut read_elements: impl FnMut(&mut Self) -> Result<()>) -> Result<()> { - self.ensure_pun('[').context("Array must start with [")?; - - let mut index = 0; + /// Parses a delimited block: `open` ... entries ... `close`. + /// + /// Calls `entry` for each item. Commas between entries are consumed automatically. + /// Handles empty blocks and trailing commas. + fn parse_block( + &mut self, + open: char, + close: char, + mut entry: impl FnMut(&mut Self) -> Result<()>, + ) -> Result<()> { + self.ensure_pun(open)?; loop { - if self.is_next(Token::Punctuation(']')) { + if self.is_next(Token::Punctuation(close)) { self.fetch_next()?; break; } - - read_elements(self).with_context(|| format!("Unable to read array element {index}"))?; - index += 1; - - match self.fetch_next()? { - Token::Punctuation(',') => continue, - Token::Punctuation(']') => break, - t => bail!("Either comma or closing bracket expected after value, got: {t:?}"), + entry(self)?; + // Consume optional comma separator. + if self.is_next(Token::Punctuation(',')) { + self.fetch_next()?; } } Ok(()) @@ -1416,7 +1385,7 @@ impl<'a> Parser<'a> { ::Err: Debug, { let mut out = Vec::new(); - self.parse_array_fn(|this| { + self.parse_block('[', ']',|this| { out.push(this.parse_token::()?); Ok(()) })?; @@ -1430,7 +1399,7 @@ impl<'a> Parser<'a> { ::Err: Debug, { let mut out = Vec::new(); - self.parse_array_fn(|this| { + self.parse_block('[', ']',|this| { out.extend(this.parse_tuple::()?); Ok(()) })?; @@ -1463,7 +1432,7 @@ impl<'a> Parser<'a> { /// Parse an array of matrices, concatenating the row-major matrices into a single vector. fn parse_matrix_array(&mut self) -> Result> { let mut matrices = Vec::new(); - self.parse_array_fn(|this| { + self.parse_block('[', ']',|this| { matrices.extend(this.parse_matrix::()?); Ok(()) })?; From 19bf95add84cb1b26368607ac52eca5b89be7863 Mon Sep 17 00:00:00 2001 From: Maksym Pavlenko Date: Tue, 7 Apr 2026 13:12:59 -0700 Subject: [PATCH 4/7] Simplify prim metadata parsing with parse_block --- src/usda/parser.rs | 70 +++++++++++----------------------------------- 1 file changed, 16 insertions(+), 54 deletions(-) diff --git a/src/usda/parser.rs b/src/usda/parser.rs index 6cef161..a4c9f24 100644 --- a/src/usda/parser.rs +++ b/src/usda/parser.rs @@ -310,30 +310,17 @@ impl<'a> Parser<'a> { let prim_path = current_path.append_path(name)?; let mut properties = Vec::new(); - let mut brace_consumed = false; - - let brace = self.fetch_next()?; - match brace { - Token::Punctuation('(') => { - self.read_prim_metadata(&mut spec, None) - .context("Unable to parse prim metadata")?; - self.ensure_pun(')').context("Prim metadata must end with )")?; - } - Token::Punctuation('{') => { - brace_consumed = true; - } - other => { - // Support metadata without wrapping parentheses. - self.read_prim_metadata(&mut spec, Some(other)) - .context("Unable to parse prim metadata")?; - brace_consumed = false; - } - }; - if !brace_consumed { - self.ensure_pun('{')?; + // Optional metadata block. + if self.is_next(Token::Punctuation('(')) { + self.parse_block('(', ')', |this| { + this.read_prim_metadata_entry(&mut spec) + .context("Unable to parse prim metadata entry") + })?; } + self.ensure_pun('{')?; + let (children, props) = self.read_prim_body(&prim_path, data)?; spec.add(ChildrenKey::PrimChildren, sdf::Value::TokenVec(children)); properties.extend(props); @@ -422,12 +409,12 @@ impl<'a> Parser<'a> { let variant_path = sdf::Path::new(&format!("{}{{{name}={variant_name}}}", prim_path))?; let mut variant_spec = sdf::Spec::new(sdf::SpecType::Variant); - // Optional metadata block: consume `(`, parse entries, consume `)`. + // Optional metadata block. if self.is_next(Token::Punctuation('(')) { - self.ensure_pun('(')?; - self.read_prim_metadata(&mut variant_spec, None) - .context("Unable to parse variant metadata")?; - self.ensure_pun(')').context("Variant metadata must end with )")?; + self.parse_block('(', ')', |this| { + this.read_prim_metadata_entry(&mut variant_spec) + .context("Unable to parse variant metadata entry") + })?; } // Variant body. @@ -735,35 +722,10 @@ impl<'a> Parser<'a> { /// Parse prim metadata contained either within parentheses or directly after the prim /// declaration (until `{` is encountered). - fn read_prim_metadata(&mut self, spec: &mut sdf::Spec, first: Option>) -> Result<()> { - let mut current = first; - - loop { - if self.is_next(Token::Punctuation(')')) || self.is_next(Token::Punctuation('{')) { - break; - } - - let token = match current.take() { - Some(token) => token, - None => self.fetch_next()?, - }; - - self.read_prim_metadata_entry(token, spec) - .context("Unable to parse prim metadata entry")?; - } - - Ok(()) - } - /// Parse a single prim metadata assignment, honoring list ops for supported fields. - fn read_prim_metadata_entry(&mut self, token: Token<'a>, spec: &mut sdf::Spec) -> Result<()> { - let (list_op, name_token) = match token { - Token::Add | Token::Append | Token::Delete | Token::Prepend | Token::Reorder => { - let name = self.fetch_next()?; - (Some(token), name) - } - _ => (None, token), - }; + fn read_prim_metadata_entry(&mut self, spec: &mut sdf::Spec) -> Result<()> { + let list_op = self.try_list_op(); + let name_token = self.fetch_next()?; let name = match name_token { Token::Identifier(s) | Token::NamespacedIdentifier(s) => s, From 3a383424fe0624ff0fe665a308c32f9913dbd7a5 Mon Sep 17 00:00:00 2001 From: Maksym Pavlenko Date: Tue, 7 Apr 2026 13:17:02 -0700 Subject: [PATCH 5/7] Use parse_block for pseudo-root and prim metadata --- src/usda/parser.rs | 50 +++++++++++++++++++--------------------------- 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/src/usda/parser.rs b/src/usda/parser.rs index a4c9f24..1a57673 100644 --- a/src/usda/parser.rs +++ b/src/usda/parser.rs @@ -173,7 +173,7 @@ impl<'a> Parser<'a> { fn one_or_list(&mut self, mut parse: impl FnMut(&mut Self) -> Result) -> Result> { if self.is_next(Token::Punctuation('[')) { let mut out = Vec::new(); - self.parse_block('[', ']',|this| { + self.parse_block('[', ']', |this| { out.push(parse(this)?); Ok(()) })?; @@ -218,9 +218,6 @@ impl<'a> Parser<'a> { return Ok(root); } - // Eat ( - self.ensure_pun('(')?; - const KNOWN_PROPS: &[(&str, Type)] = &[ (FieldKey::DefaultPrim.as_str(), Type::Token), (FieldKey::StartTimeCode.as_str(), Type::Uint64), @@ -233,36 +230,33 @@ impl<'a> Parser<'a> { ("upAxis", Type::Token), ]; - // Read pseudo root properties - loop { - let next = self.fetch_next().context("Unable to fetch next pseudo root property")?; + self.parse_block('(', ')', |this| { + let next = this.fetch_next().context("Unable to fetch next pseudo root property")?; match next { - Token::Punctuation(')') => break, Token::String(str) => { root.add(FieldKey::Documentation, str); } Token::Doc => { - self.ensure_pun('=')?; - let value = self.fetch_str()?; + this.ensure_pun('=')?; + let value = this.fetch_str()?; root.add("doc", value); } Token::SubLayers => { - self.ensure_pun('=')?; - let (sublayers, sublayer_offsets) = self.parse_sublayers().context("Unable to parse subLayers")?; + this.ensure_pun('=')?; + let (sublayers, sublayer_offsets) = this.parse_sublayers().context("Unable to parse subLayers")?; root.add(FieldKey::SubLayers, sublayers); root.add(FieldKey::SubLayerOffsets, sublayer_offsets); } Token::Identifier(name) => { + this.ensure_pun('=')?; if let Some((known_name, ty)) = KNOWN_PROPS.iter().copied().find(|(n, _)| *n == name) { - self.ensure_pun('=')?; - let value = self + let value = this .parse_value(ty) .with_context(|| format!("Unable to parse value for {known_name}"))?; root.add(known_name, value); } else { - self.ensure_pun('=')?; - let value = self + let value = this .parse_property_metadata_value() .with_context(|| format!("Unable to parse pseudo root metadata value for {name}"))?; root.fields.insert(name.to_owned(), value); @@ -270,7 +264,8 @@ impl<'a> Parser<'a> { } _ => bail!("Unexpected token {next:?}"), } - } + Ok(()) + })?; Ok(root) } @@ -576,7 +571,7 @@ impl<'a> Parser<'a> { // Handle array case first by peeking, so parse_block can consume the '[' if self.is_next(Token::Punctuation('[')) { let mut values = Vec::new(); - self.parse_block('[', ']',|this| { + self.parse_block('[', ']', |this| { let entry = this.fetch_next()?; let value = match entry { Token::String(v) => v.to_owned(), @@ -1194,7 +1189,7 @@ impl<'a> Parser<'a> { /// Parse an array of booleans, reusing the permissive literal parsing rules. fn parse_bool_array(&mut self) -> Result> { let mut out = Vec::new(); - self.parse_block('[', ']',|this| { + self.parse_block('[', ']', |this| { out.push(this.parse_bool()?); Ok(()) })?; @@ -1211,7 +1206,7 @@ impl<'a> Parser<'a> { fn parse_asset_path_array(&mut self) -> Result> { let mut result = Vec::new(); - self.parse_block('[', ']',|this| { + self.parse_block('[', ']', |this| { result.push(this.parse_asset_path()?); Ok(()) })?; @@ -1223,7 +1218,7 @@ impl<'a> Parser<'a> { let mut sublayers = Vec::new(); let mut sublayer_offsets = Vec::new(); - self.parse_block('[', ']',|this| { + self.parse_block('[', ']', |this| { let asset_path = this .fetch_next()? .try_as_asset_ref() @@ -1277,12 +1272,7 @@ impl<'a> Parser<'a> { /// /// Calls `entry` for each item. Commas between entries are consumed automatically. /// Handles empty blocks and trailing commas. - fn parse_block( - &mut self, - open: char, - close: char, - mut entry: impl FnMut(&mut Self) -> Result<()>, - ) -> Result<()> { + fn parse_block(&mut self, open: char, close: char, mut entry: impl FnMut(&mut Self) -> Result<()>) -> Result<()> { self.ensure_pun(open)?; loop { if self.is_next(Token::Punctuation(close)) { @@ -1347,7 +1337,7 @@ impl<'a> Parser<'a> { ::Err: Debug, { let mut out = Vec::new(); - self.parse_block('[', ']',|this| { + self.parse_block('[', ']', |this| { out.push(this.parse_token::()?); Ok(()) })?; @@ -1361,7 +1351,7 @@ impl<'a> Parser<'a> { ::Err: Debug, { let mut out = Vec::new(); - self.parse_block('[', ']',|this| { + self.parse_block('[', ']', |this| { out.extend(this.parse_tuple::()?); Ok(()) })?; @@ -1394,7 +1384,7 @@ impl<'a> Parser<'a> { /// Parse an array of matrices, concatenating the row-major matrices into a single vector. fn parse_matrix_array(&mut self) -> Result> { let mut matrices = Vec::new(); - self.parse_block('[', ']',|this| { + self.parse_block('[', ']', |this| { matrices.extend(this.parse_matrix::()?); Ok(()) })?; From cb6ed3cfd2cfc41f27ac02e47e3d4e9d2adf1b14 Mon Sep 17 00:00:00 2001 From: Maksym Pavlenko Date: Tue, 7 Apr 2026 13:19:17 -0700 Subject: [PATCH 6/7] Use parse_block for prim body --- src/usda/parser.rs | 33 ++++++++++----------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/src/usda/parser.rs b/src/usda/parser.rs index 1a57673..e9f160c 100644 --- a/src/usda/parser.rs +++ b/src/usda/parser.rs @@ -314,8 +314,6 @@ impl<'a> Parser<'a> { })?; } - self.ensure_pun('{')?; - let (children, props) = self.read_prim_body(&prim_path, data)?; spec.add(ChildrenKey::PrimChildren, sdf::Value::TokenVec(children)); properties.extend(props); @@ -327,10 +325,9 @@ impl<'a> Parser<'a> { Ok(()) } - /// Parse the body of a prim or variant (everything between `{` and `}`). + /// Parse the body of a prim or variant (`{ ... }`). /// /// Returns the child prim names and property names found in the body. - /// The opening `{` must already be consumed; this method consumes the closing `}`. fn read_prim_body( &mut self, path: &sdf::Path, @@ -339,33 +336,24 @@ impl<'a> Parser<'a> { let mut children = Vec::new(); let mut properties = Vec::new(); - loop { - let next = self - .peek_next() - .context("Unexpected end of prim body")? - .as_ref() - .map_err(|e| anyhow!("{e:?}"))?; - - match next { - Token::Punctuation('}') => { - self.fetch_next()?; - break; - } + self.parse_block('{', '}', |this| { + match this.peek_next().context("Unexpected end of prim body")?.as_ref().map_err(|e| anyhow!("{e:?}"))? { Token::Def | Token::Over | Token::Class => { - self.read_prim(path, &mut children, data)?; + this.read_prim(path, &mut children, data)?; } Token::VariantSet => { - self.read_variant_set(path, data)?; + this.read_variant_set(path, data)?; } Token::Rel => { - self.fetch_next()?; - self.read_relationship(path, &mut properties, data)?; + this.fetch_next()?; + this.read_relationship(path, &mut properties, data)?; } _ => { - self.read_attribute(path, &mut properties, data)?; + this.read_attribute(path, &mut properties, data)?; } } - } + Ok(()) + })?; Ok((children, properties)) } @@ -413,7 +401,6 @@ impl<'a> Parser<'a> { } // Variant body. - self.ensure_pun('{')?; let (children, properties) = self.read_prim_body(&variant_path, data)?; variant_spec.add(ChildrenKey::PrimChildren, sdf::Value::TokenVec(children)); variant_spec.add(ChildrenKey::PropertyChildren, sdf::Value::TokenVec(properties)); From 946e8434dd640e7fa257c05cc7265d0416c58a68 Mon Sep 17 00:00:00 2001 From: Maksym Pavlenko Date: Tue, 7 Apr 2026 13:23:25 -0700 Subject: [PATCH 7/7] Use parse_block for variant set, reuse fetch_str --- src/usda/parser.rs | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/src/usda/parser.rs b/src/usda/parser.rs index e9f160c..a0ae8dc 100644 --- a/src/usda/parser.rs +++ b/src/usda/parser.rs @@ -337,7 +337,12 @@ impl<'a> Parser<'a> { let mut properties = Vec::new(); self.parse_block('{', '}', |this| { - match this.peek_next().context("Unexpected end of prim body")?.as_ref().map_err(|e| anyhow!("{e:?}"))? { + match this + .peek_next() + .context("Unexpected end of prim body")? + .as_ref() + .map_err(|e| anyhow!("{e:?}"))? + { Token::Def | Token::Over | Token::Class => { this.read_prim(path, &mut children, data)?; } @@ -365,14 +370,8 @@ impl<'a> Parser<'a> { fn read_variant_set(&mut self, prim_path: &sdf::Path, data: &mut HashMap) -> Result<()> { self.fetch_next()?; // consume `variantSet` - let name = self - .fetch_next()? - .try_as_string() - .ok_or_else(|| anyhow!("Expected variant set name string"))? - .to_string(); - + let name = self.fetch_str().context("Expected variant set name")?.to_string(); self.ensure_pun('=')?; - self.ensure_pun('{')?; // Create the variant set spec. let vset_path = sdf::Path::new(&format!("{}{{{name}=}}", prim_path))?; @@ -380,12 +379,8 @@ impl<'a> Parser<'a> { let mut variant_children = Vec::new(); // Parse each variant: "VariantName" (...) { ... } - while !self.is_next(Token::Punctuation('}')) { - let variant_name = self - .fetch_next()? - .try_as_string() - .ok_or_else(|| anyhow!("Expected variant name string"))? - .to_string(); + self.parse_block('{', '}', |this| { + let variant_name = this.fetch_str().context("Expected variant name")?.to_string(); variant_children.push(variant_name.clone()); @@ -393,21 +388,20 @@ impl<'a> Parser<'a> { let mut variant_spec = sdf::Spec::new(sdf::SpecType::Variant); // Optional metadata block. - if self.is_next(Token::Punctuation('(')) { - self.parse_block('(', ')', |this| { + if this.is_next(Token::Punctuation('(')) { + this.parse_block('(', ')', |this| { this.read_prim_metadata_entry(&mut variant_spec) .context("Unable to parse variant metadata entry") })?; } // Variant body. - let (children, properties) = self.read_prim_body(&variant_path, data)?; + let (children, properties) = this.read_prim_body(&variant_path, data)?; variant_spec.add(ChildrenKey::PrimChildren, sdf::Value::TokenVec(children)); variant_spec.add(ChildrenKey::PropertyChildren, sdf::Value::TokenVec(properties)); data.insert(variant_path, variant_spec); - } - - self.ensure_pun('}')?; // close the variant set + Ok(()) + })?; vset_spec.add(ChildrenKey::VariantChildren, sdf::Value::TokenVec(variant_children)); data.insert(vset_path, vset_spec);