// // Copyright 2016 Pixar // // Licensed under the Apache License, Version 2.0 (the "Apache License") // with the following modification; you may not use this file except in // compliance with the Apache License and the following modification to it: // Section 6. Trademarks. is deleted and replaced with: // // 6. Trademarks. This License does not grant permission to use the trade // names, trademarks, service marks, or product names of the Licensor // and its affiliates, except as required to comply with Section 4(c) of // the License and to reproduce the content of the NOTICE file. // // You may obtain a copy of the Apache License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the Apache License with the above modification is // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the Apache License for the specific // language governing permissions and limitations under the Apache License. // %{ #include "pxr/pxr.h" #include "pxr/base/arch/errno.h" #include "pxr/base/arch/fileSystem.h" #include "pxr/base/vt/array.h" #include "pxr/base/vt/dictionary.h" #include "pxr/usd/ar/asset.h" #include "pxr/usd/sdf/allowed.h" #include "pxr/usd/sdf/data.h" #include "pxr/usd/sdf/fileIO_Common.h" #include "pxr/usd/sdf/layerOffset.h" #include "pxr/usd/sdf/listOp.h" #include "pxr/usd/sdf/textParserContext.h" #include "pxr/usd/sdf/parserValueContext.h" #include "pxr/usd/sdf/payload.h" #include "pxr/usd/sdf/reference.h" #include "pxr/usd/sdf/schema.h" #include "pxr/usd/sdf/types.h" #include "pxr/base/trace/trace.h" #include "pxr/base/arch/errno.h" #include "pxr/base/tf/enum.h" #include "pxr/base/tf/iterator.h" #include "pxr/base/tf/ostreamMethods.h" #include "pxr/base/tf/stringUtils.h" #include "pxr/base/tf/type.h" #include "pxr/base/gf/matrix4d.h" #include "pxr/base/tf/mallocTag.h" #include #include #include #include #include #include #include // See this page for info as to why this is here. Especially note the last // paragraph. http://www.delorie.com/gnu/docs/bison/bison_91.html #define YYINITDEPTH 1500 PXR_NAMESPACE_USING_DIRECTIVE using Sdf_ParserHelpers::Value; using boost::get; //-------------------------------------------------------------------- // Helper macros/functions for handling errors //-------------------------------------------------------------------- #define ABORT_IF_ERROR(seenError) if (seenError) YYABORT #define Err(context, ...) \ textFileFormatYyerror(context, TfStringPrintf(__VA_ARGS__).c_str()) #define ERROR_IF_NOT_ALLOWED(context, allowed) \ { \ const SdfAllowed allow = allowed; \ if (!allow) { \ Err(context, "%s", allow.GetWhyNot().c_str()); \ } \ } #define ERROR_AND_RETURN_IF_NOT_ALLOWED(context, allowed) \ { \ const SdfAllowed allow = allowed; \ if (!allow) { \ Err(context, "%s", allow.GetWhyNot().c_str()); \ return; \ } \ } //-------------------------------------------------------------------- // Extern declarations to scanner data and functions //-------------------------------------------------------------------- #define YYSTYPE Sdf_ParserHelpers::Value // Opaque buffer type handle. struct yy_buffer_state; // Generated bison symbols. void textFileFormatYyerror(Sdf_TextParserContext *context, const char *s); extern int textFileFormatYylex(YYSTYPE *yylval_param, yyscan_t yyscanner); extern char *textFileFormatYyget_text(yyscan_t yyscanner); extern size_t textFileFormatYyget_leng(yyscan_t yyscanner); extern int textFileFormatYylex_init(yyscan_t *yyscanner); extern int textFileFormatYylex_destroy(yyscan_t yyscanner); extern void textFileFormatYyset_extra(Sdf_TextParserContext *context, yyscan_t yyscanner); extern yy_buffer_state *textFileFormatYy_scan_buffer(char *yy_str, size_t size, yyscan_t yyscanner); extern yy_buffer_state *textFileFormatYy_scan_string(const char *yy_str, yyscan_t yyscanner); extern yy_buffer_state *textFileFormatYy_scan_bytes(const char *yy_str, size_t numBytes, yyscan_t yyscanner); extern void textFileFormatYy_delete_buffer(yy_buffer_state *b, yyscan_t yyscanner); #define yyscanner context->scanner //-------------------------------------------------------------------- // Helpers //-------------------------------------------------------------------- static bool _SetupValue(const std::string& typeName, Sdf_TextParserContext *context) { return context->values.SetupFactory(typeName); } template static bool _HasDuplicates(const std::vector &v) { std::set s; TF_FOR_ALL(i, v) { if (!s.insert(*i).second) { return true; } } return false; } namespace { template const std::vector& _ToItemVector(const std::vector& v) { return v; } template std::vector _ToItemVector(const VtArray& v) { return std::vector(v.begin(), v.end()); } } // Set a single ListOp vector in the list op for the current // path and specified key. template static void _SetListOpItems(const TfToken &key, SdfListOpType type, const T &itemList, Sdf_TextParserContext *context) { typedef SdfListOp ListOpType; typedef typename ListOpType::ItemVector ItemVector; const ItemVector& items = _ToItemVector(itemList); if (_HasDuplicates(items)) { Err(context, "Duplicate items exist for field '%s' at '%s'", key.GetText(), context->path.GetText()); } ListOpType op = context->data->GetAs(context->path, key); op.SetItems(items, type); context->data->Set(context->path, key, VtValue::Take(op)); } // Append a single item to the vector for the current path and specified key. template static void _AppendVectorItem(const TfToken& key, const T& item, Sdf_TextParserContext *context) { std::vector vec = context->data->GetAs >(context->path, key); vec.push_back(item); context->data->Set(context->path, key, VtValue(vec)); } template inline static void _SetField(const SdfPath& path, const TfToken& key, const T& item, Sdf_TextParserContext *context) { context->data->Set(path, key, VtValue(item)); } inline static bool _HasField(const SdfPath& path, const TfToken& key, VtValue* value, Sdf_TextParserContext *context) { return context->data->Has(path, key, value); } inline static bool _HasSpec(const SdfPath& path, Sdf_TextParserContext *context) { return context->data->HasSpec(path); } inline static void _CreateSpec(const SdfPath& path, SdfSpecType specType, Sdf_TextParserContext *context) { context->data->CreateSpec(path, specType); } static void _MatchMagicIdentifier(const Value& arg1, Sdf_TextParserContext *context) { const std::string cookie = TfStringTrimRight(arg1.Get()); const std::string expected = "#" + context->magicIdentifierToken + " "; if (TfStringStartsWith(cookie, expected)) { if (!context->versionString.empty() && !TfStringEndsWith(cookie, context->versionString)) { TF_WARN("File '%s' is not the latest %s version (found '%s', " "expected '%s'). The file may parse correctly and yield " "incorrect results.", context->fileContext.c_str(), context->magicIdentifierToken.c_str(), cookie.substr(expected.length()).c_str(), context->versionString.c_str()); } } else { Err(context, "Magic Cookie '%s'. Expected prefix of '%s'", TfStringTrim(cookie).c_str(), expected.c_str()); } } static SdfPermission _GetPermissionFromString(const std::string & str, Sdf_TextParserContext *context) { if (str == "public") { return SdfPermissionPublic; } else if (str == "private") { return SdfPermissionPrivate; } else { Err(context, "'%s' is not a valid permission constant", str.c_str()); return SdfPermissionPublic; } } static TfEnum _GetDisplayUnitFromString(const std::string & name, Sdf_TextParserContext *context) { const TfEnum &unit = SdfGetUnitFromName(name); if (unit == TfEnum()) { Err(context, "'%s' is not a valid display unit", name.c_str()); } return unit; } static void _ValueAppendAtomic(const Value& arg1, Sdf_TextParserContext *context) { context->values.AppendValue(arg1); } static void _ValueSetAtomic(Sdf_TextParserContext *context) { if (!context->values.IsRecordingString()) { if (context->values.valueIsShaped) { Err(context, "Type name has [] for non-shaped value!\n"); return; } } std::string errStr; context->currentValue = context->values.ProduceValue(&errStr); if (context->currentValue.IsEmpty()) { Err(context, "Error parsing simple value: %s", errStr.c_str()); return; } } static void _PrimSetInheritListItems(SdfListOpType opType, Sdf_TextParserContext *context) { if (context->inheritParsingTargetPaths.empty() && opType != SdfListOpTypeExplicit) { Err(context, "Setting inherit paths to None (or empty list) is only allowed " "when setting explicit inherit paths, not for list editing"); return; } TF_FOR_ALL(path, context->inheritParsingTargetPaths) { ERROR_AND_RETURN_IF_NOT_ALLOWED( context, SdfSchema::IsValidInheritPath(*path)); } _SetListOpItems(SdfFieldKeys->InheritPaths, opType, context->inheritParsingTargetPaths, context); } static void _InheritAppendPath(Sdf_TextParserContext *context) { // Expand paths relative to the containing prim. // // This strips any variant selections from the containing prim // path before expanding the relative path, which is what we // want. Inherit paths are not allowed to be variants. SdfPath absPath = context->savedPath.MakeAbsolutePath(context->path.GetPrimPath()); context->inheritParsingTargetPaths.push_back(absPath); } static void _PrimSetSpecializesListItems(SdfListOpType opType, Sdf_TextParserContext *context) { if (context->specializesParsingTargetPaths.empty() && opType != SdfListOpTypeExplicit) { Err(context, "Setting specializes paths to None (or empty list) is only allowed " "when setting explicit specializes paths, not for list editing"); return; } TF_FOR_ALL(path, context->specializesParsingTargetPaths) { ERROR_AND_RETURN_IF_NOT_ALLOWED( context, SdfSchema::IsValidSpecializesPath(*path)); } _SetListOpItems(SdfFieldKeys->Specializes, opType, context->specializesParsingTargetPaths, context); } static void _SpecializesAppendPath(Sdf_TextParserContext *context) { // Expand paths relative to the containing prim. // // This strips any variant selections from the containing prim // path before expanding the relative path, which is what we // want. Specializes paths are not allowed to be variants. SdfPath absPath = context->savedPath.MakeAbsolutePath(context->path.GetPrimPath()); context->specializesParsingTargetPaths.push_back(absPath); } static void _PrimSetReferenceListItems(SdfListOpType opType, Sdf_TextParserContext *context) { if (context->referenceParsingRefs.empty() && opType != SdfListOpTypeExplicit) { Err(context, "Setting references to None (or an empty list) is only allowed " "when setting explicit references, not for list editing"); return; } TF_FOR_ALL(ref, context->referenceParsingRefs) { ERROR_AND_RETURN_IF_NOT_ALLOWED( context, SdfSchema::IsValidReference(*ref)); } _SetListOpItems(SdfFieldKeys->References, opType, context->referenceParsingRefs, context); } static void _PrimSetPayloadListItems(SdfListOpType opType, Sdf_TextParserContext *context) { if (context->payloadParsingRefs.empty() && opType != SdfListOpTypeExplicit) { Err(context, "Setting payload to None (or an empty list) is only allowed " "when setting explicit payloads, not for list editing"); return; } TF_FOR_ALL(ref, context->payloadParsingRefs) { ERROR_AND_RETURN_IF_NOT_ALLOWED( context, SdfSchema::IsValidPayload(*ref)); } _SetListOpItems(SdfFieldKeys->Payload, opType, context->payloadParsingRefs, context); } static void _PrimSetVariantSetNamesListItems(SdfListOpType opType, Sdf_TextParserContext *context) { std::vector names; names.reserve(context->nameVector.size()); TF_FOR_ALL(name, context->nameVector) { ERROR_AND_RETURN_IF_NOT_ALLOWED( context, SdfSchema::IsValidVariantIdentifier(*name)); names.push_back(name->GetText()); } _SetListOpItems(SdfFieldKeys->VariantSetNames, opType, names, context); // If the op type is added or explicit, create the variant sets if (opType == SdfListOpTypeAdded || opType == SdfListOpTypeExplicit) { TF_FOR_ALL(i, context->nameVector) { _CreateSpec( context->path.AppendVariantSelection(*i,""), SdfSpecTypeVariantSet, context); } _SetField( context->path, SdfChildrenKeys->VariantSetChildren, context->nameVector, context); } } static void _RelationshipInitTarget(const SdfPath& targetPath, Sdf_TextParserContext *context) { SdfPath path = context->path.AppendTarget(targetPath); if (!_HasSpec(path, context)) { // Create relationship target spec by setting the appropriate // object type flag. _CreateSpec(path, SdfSpecTypeRelationshipTarget, context); // Add the target path to the owning relationship's list of target // children. context->relParsingNewTargetChildren.push_back(targetPath); } } static void _RelationshipSetTargetsList(SdfListOpType opType, Sdf_TextParserContext *context) { if (!context->relParsingTargetPaths) { // No target paths were encountered. return; } if (context->relParsingTargetPaths->empty() && opType != SdfListOpTypeExplicit) { Err(context, "Setting relationship targets to None (or empty list) is only " "allowed when setting explicit targets, not for list editing"); return; } TF_FOR_ALL(path, *context->relParsingTargetPaths) { ERROR_AND_RETURN_IF_NOT_ALLOWED( context, SdfSchema::IsValidRelationshipTargetPath(*path)); } if (opType == SdfListOpTypeAdded || opType == SdfListOpTypeExplicit) { // Initialize relationship target specs for each target path that // is added in this layer. TF_FOR_ALL(pathIter, *context->relParsingTargetPaths) { _RelationshipInitTarget(*pathIter, context); } } _SetListOpItems(SdfFieldKeys->TargetPaths, opType, *context->relParsingTargetPaths, context); } static void _PrimSetVariantSelection(Sdf_TextParserContext *context) { SdfVariantSelectionMap refVars; // The previous parser implementation allowed multiple variant selection // dictionaries in prim metadata to be merged, so we do the same here. VtValue oldVars; if (_HasField( context->path, SdfFieldKeys->VariantSelection, &oldVars, context)) { refVars = oldVars.Get(); } TF_FOR_ALL(it, context->currentDictionaries[0]) { if (!it->second.IsHolding()) { Err(context, "variant name must be a string"); return; } else { const std::string variantName = it->second.Get(); ERROR_AND_RETURN_IF_NOT_ALLOWED( context, SdfSchema::IsValidVariantIdentifier(variantName)); refVars[it->first] = variantName; } } _SetField(context->path, SdfFieldKeys->VariantSelection, refVars, context); context->currentDictionaries[0].clear(); } static void _RelocatesAdd(const Value& arg1, const Value& arg2, Sdf_TextParserContext *context) { const std::string& srcStr = arg1.Get(); const std::string& targetStr = arg2.Get(); SdfPath srcPath(srcStr); SdfPath targetPath(targetStr); if (!srcPath.IsPrimPath()) { Err(context, "'%s' is not a valid prim path", srcStr.c_str()); return; } if (!targetPath.IsPrimPath()) { Err(context, "'%s' is not a valid prim path", targetStr.c_str()); return; } // The relocates map is expected to only hold absolute paths. The // SdRelocatesMapProxy ensures that all paths are made absolute when // editing, but since we're bypassing that proxy and setting the map // directly into the underlying SdfData, we need to explicitly absolutize // paths here. const SdfPath srcAbsPath = srcPath.MakeAbsolutePath(context->path); const SdfPath targetAbsPath = targetPath.MakeAbsolutePath(context->path); context->relocatesParsingMap.insert(std::make_pair(srcAbsPath, targetAbsPath)); } static void _AttributeSetConnectionTargetsList(SdfListOpType opType, Sdf_TextParserContext *context) { if (context->connParsingTargetPaths.empty() && opType != SdfListOpTypeExplicit) { Err(context, "Setting connection paths to None (or an empty list) " "is only allowed when setting explicit connection paths, " "not for list editing"); return; } TF_FOR_ALL(path, context->connParsingTargetPaths) { ERROR_AND_RETURN_IF_NOT_ALLOWED( context, SdfSchema::IsValidAttributeConnectionPath(*path)); } if (opType == SdfListOpTypeAdded || opType == SdfListOpTypeExplicit) { TF_FOR_ALL(pathIter, context->connParsingTargetPaths) { SdfPath path = context->path.AppendTarget(*pathIter); if (!_HasSpec(path, context)) { _CreateSpec(path, SdfSpecTypeConnection, context); } } _SetField( context->path, SdfChildrenKeys->ConnectionChildren, context->connParsingTargetPaths, context); } _SetListOpItems(SdfFieldKeys->ConnectionPaths, opType, context->connParsingTargetPaths, context); } static void _AttributeAppendConnectionPath(Sdf_TextParserContext *context) { // Expand paths relative to the containing prim. // // This strips any variant selections from the containing prim // path before expanding the relative path, which is what we // want. Connection paths never point into the variant namespace. SdfPath absPath = context->savedPath.MakeAbsolutePath(context->path.GetPrimPath()); // XXX Workaround for bug 68132: // Prior to the fix to bug 67916, FilterGenVariantBase was authoring // invalid connection paths containing variant selections (which // Sd was failing to report as erroneous). As a result, there's // a fair number of assets out there with these broken forms of // connection paths. As a migration measure, we discard those // variant selections here. if (absPath.ContainsPrimVariantSelection()) { TF_WARN("Connection path <%s> (in file @%s@, line %i) has a variant " "selection, but variant selections are not meaningful in " "connection paths. Stripping the variant selection and " "using <%s> instead. Resaving the file will fix this issue.", absPath.GetText(), context->fileContext.c_str(), context->menvaLineNo, absPath.StripAllVariantSelections().GetText()); absPath = absPath.StripAllVariantSelections(); } context->connParsingTargetPaths.push_back(absPath); } static void _PrimInitAttribute(const Value &arg1, Sdf_TextParserContext *context) { TfToken name(arg1.Get()); if (!SdfPath::IsValidNamespacedIdentifier(name)) { Err(context, "'%s' is not a valid attribute name", name.GetText()); } context->path = context->path.AppendProperty(name); // If we haven't seen this attribute before, then set the object type // and add it to the parent's list of properties. Otherwise both have // already been done, so we don't need to do anything. if (!_HasSpec(context->path, context)) { context->propertiesStack.back().push_back(name); _CreateSpec(context->path, SdfSpecTypeAttribute, context); _SetField(context->path, SdfFieldKeys->Custom, false, context); } if(context->custom) _SetField(context->path, SdfFieldKeys->Custom, true, context); // If the type was previously set, check that it matches. Otherwise set it. const TfToken newType(context->values.valueTypeName); VtValue oldTypeValue; if (_HasField( context->path, SdfFieldKeys->TypeName, &oldTypeValue, context)) { const TfToken& oldType = oldTypeValue.Get(); if (newType != oldType) { Err(context, "attribute '%s' already has type '%s', cannot change to '%s'", context->path.GetName().c_str(), oldType.GetText(), newType.GetText()); } } else { _SetField(context->path, SdfFieldKeys->TypeName, newType, context); } // If the variability was previously set, check that it matches. Otherwise // set it. If the 'variability' VtValue is empty, that indicates varying // variability. SdfVariability variability = context->variability.IsEmpty() ? SdfVariabilityVarying : context->variability.Get(); VtValue oldVariability; if (_HasField( context->path, SdfFieldKeys->Variability, &oldVariability, context)) { if (variability != oldVariability.Get()) { Err(context, "attribute '%s' already has variability '%s', " "cannot change to '%s'", context->path.GetName().c_str(), TfEnum::GetName(oldVariability.Get()).c_str(), TfEnum::GetName(variability).c_str() ); } } else { _SetField( context->path, SdfFieldKeys->Variability, variability, context); } } static void _DictionaryBegin(Sdf_TextParserContext *context) { context->currentDictionaries.push_back(VtDictionary()); // Whenever we parse a value for an unregistered generic metadata field, // the parser value context records the string representation only, because // we don't have enough type information to generate a C++ value. However, // dictionaries are a special case because we have all the type information // we need to generate C++ values. So, override the previous setting. if (context->values.IsRecordingString()) { context->values.StopRecordingString(); } } static void _DictionaryEnd(Sdf_TextParserContext *context) { context->currentDictionaries.pop_back(); } static void _DictionaryInsertValue(const Value& arg1, Sdf_TextParserContext *context) { size_t n = context->currentDictionaries.size(); context->currentDictionaries[n-2][arg1.Get()] = context->currentValue; } static void _DictionaryInsertDictionary(const Value& arg1, Sdf_TextParserContext *context) { size_t n = context->currentDictionaries.size(); // Insert the parsed dictionary into the parent dictionary. context->currentDictionaries[n-2][arg1.Get()].Swap( context->currentDictionaries[n-1]); // Clear out the last dictionary (there can be more dictionaries on the // same nesting level). context->currentDictionaries[n-1].clear(); } static void _DictionaryInitScalarFactory(const Value& arg1, Sdf_TextParserContext *context) { const std::string& typeName = arg1.Get(); if (!_SetupValue(typeName, context)) { Err(context, "Unrecognized value typename '%s' for dictionary", typeName.c_str()); } } static void _DictionaryInitShapedFactory(const Value& arg1, Sdf_TextParserContext *context) { const std::string typeName = arg1.Get() + "[]"; if (!_SetupValue(typeName, context)) { Err(context, "Unrecognized value typename '%s' for dictionary", typeName.c_str()); } } static void _ValueSetTuple(Sdf_TextParserContext *context) { if (!context->values.IsRecordingString()) { if (context->values.valueIsShaped) { Err(context, "Type name has [] for non-shaped value.\n"); return; } } std::string errStr; context->currentValue = context->values.ProduceValue(&errStr); if (context->currentValue == VtValue()) { Err(context, "Error parsing tuple value: %s", errStr.c_str()); return; } } static void _ValueSetList(Sdf_TextParserContext *context) { if (!context->values.IsRecordingString()) { if (!context->values.valueIsShaped) { Err(context, "Type name missing [] for shaped value."); return; } } std::string errStr; context->currentValue = context->values.ProduceValue(&errStr); if (context->currentValue == VtValue()) { Err(context, "Error parsing shaped value: %s", errStr.c_str()); return; } } static void _ValueSetShaped(Sdf_TextParserContext *context) { if (!context->values.IsRecordingString()) { if (!context->values.valueIsShaped) { Err(context, "Type name missing [] for shaped value."); return; } } std::string errStr; context->currentValue = context->values.ProduceValue(&errStr); if (context->currentValue == VtValue()) { // The factory method ProduceValue() uses for shaped types // only returns empty VtArrays, not empty VtValues, so this // is impossible to hit currently. // CODE_COVERAGE_OFF Err(context, "Error parsing shaped value: %s", errStr.c_str()); // CODE_COVERAGE_OFF_GCOV_BUG // The following line actually shows as executed (a ridiculous // number of times) even though the line above shwos as // not executed return; // CODE_COVERAGE_ON_GCOV_BUG // CODE_COVERAGE_ON } } static void _ValueSetCurrentToSdfPath(const Value& arg1, Sdf_TextParserContext *context) { // make current Value an SdfPath of the given argument... std::string s = arg1.Get(); // If path is empty, use default c'tor to construct empty path. // XXX: 08/04/08 Would be nice if SdfPath would allow // SdfPath("") without throwing a warning. context->currentValue = s.empty() ? SdfPath() : SdfPath(s); } static void _PrimInitRelationship(const Value& arg1, Sdf_TextParserContext *context) { TfToken name( arg1.Get() ); if (!SdfPath::IsValidNamespacedIdentifier(name)) { Err(context, "'%s' is not a valid relationship name", name.GetText()); return; } context->path = context->path.AppendProperty(name); if (!_HasSpec(context->path, context)) { context->propertiesStack.back().push_back(name); _CreateSpec(context->path, SdfSpecTypeRelationship, context); } _SetField( context->path, SdfFieldKeys->Variability, context->variability, context); if (context->custom) { _SetField(context->path, SdfFieldKeys->Custom, context->custom, context); } context->relParsingAllowTargetData = false; context->relParsingTargetPaths.reset(); context->relParsingNewTargetChildren.clear(); } static void _PrimEndRelationship(Sdf_TextParserContext *context) { if (!context->relParsingNewTargetChildren.empty()) { std::vector children = context->data->GetAs >( context->path, SdfChildrenKeys->RelationshipTargetChildren); children.insert(children.end(), context->relParsingNewTargetChildren.begin(), context->relParsingNewTargetChildren.end()); _SetField( context->path, SdfChildrenKeys->RelationshipTargetChildren, children, context); } context->path = context->path.GetParentPath(); } static void _RelationshipAppendTargetPath(const Value& arg1, Sdf_TextParserContext *context) { // Add a new target to the current relationship const std::string& pathStr = arg1.Get(); SdfPath path(pathStr); if (!path.IsAbsolutePath()) { // Expand paths relative to the containing prim. // // This strips any variant selections from the containing prim // path before expanding the relative path, which is what we // want. Target paths never point into the variant namespace. path = path.MakeAbsolutePath(context->path.GetPrimPath()); } if (!context->relParsingTargetPaths) { // This is the first target we've seen for this relationship. // Start tracking them in a vector. context->relParsingTargetPaths = SdfPathVector(); } context->relParsingTargetPaths->push_back(path); } static void _PathSetPrim(const Value& arg1, Sdf_TextParserContext *context) { const std::string& pathStr = arg1.Get(); context->savedPath = SdfPath(pathStr); if (!context->savedPath.IsPrimPath()) { Err(context, "'%s' is not a valid prim path", pathStr.c_str()); } } static void _PathSetPrimOrPropertyScenePath(const Value& arg1, Sdf_TextParserContext *context) { const std::string& pathStr = arg1.Get(); context->savedPath = SdfPath(pathStr); // Valid paths are prim or property paths that do not contain variant // selections. SdfPath const &path = context->savedPath; bool pathValid = (path.IsPrimPath() || path.IsPropertyPath()) && !path.ContainsPrimVariantSelection(); if (!pathValid) { Err(context, "'%s' is not a valid prim or property scene path", pathStr.c_str()); } } template static bool _SetItemsIfListOp(const TfType& type, Sdf_TextParserContext *context) { if (!type.IsA()) { return false; } typedef VtArray ArrayType; if (!TF_VERIFY(context->currentValue.IsHolding() || context->currentValue.IsEmpty())) { return true; } ArrayType vtArray; if (context->currentValue.IsHolding()) { vtArray = context->currentValue.UncheckedGet(); } _SetListOpItems( context->genericMetadataKey, context->listOpType, vtArray, context); return true; } static void _SetGenericMetadataListOpItems(const TfType& fieldType, Sdf_TextParserContext *context) { // Chain together attempts to set list op items using 'or' to bail // out as soon as we successfully write out the list op we're holding. _SetItemsIfListOp(fieldType, context) || _SetItemsIfListOp(fieldType, context) || _SetItemsIfListOp(fieldType, context) || _SetItemsIfListOp(fieldType, context) || _SetItemsIfListOp(fieldType, context) || _SetItemsIfListOp(fieldType, context); } template static bool _IsListOpType(const TfType& type, TfType* itemArrayType = nullptr) { if (type.IsA()) { if (itemArrayType) { typedef VtArray ArrayType; *itemArrayType = TfType::Find(); } return true; } return false; } static bool _IsGenericMetadataListOpType(const TfType& type, TfType* itemArrayType = nullptr) { return _IsListOpType(type, itemArrayType) || _IsListOpType(type, itemArrayType) || _IsListOpType(type, itemArrayType) || _IsListOpType(type, itemArrayType) || _IsListOpType(type, itemArrayType) || _IsListOpType(type, itemArrayType); } static void _GenericMetadataStart(const Value &name, SdfSpecType specType, Sdf_TextParserContext *context) { context->genericMetadataKey = TfToken(name.Get()); context->listOpType = SdfListOpTypeExplicit; const SdfSchema& schema = SdfSchema::GetInstance(); const SdfSchema::SpecDefinition &specDef = *schema.GetSpecDefinition(specType); if (specDef.IsMetadataField(context->genericMetadataKey)) { // Prepare to parse a known field const SdfSchema::FieldDefinition &fieldDef = *schema.GetFieldDefinition(context->genericMetadataKey); const TfType fieldType = fieldDef.GetFallbackValue().GetType(); // For list op-valued metadata fields, set up the parser as if // we were parsing an array of the list op's underlying type. // In _GenericMetadataEnd, we'll produce this list and set it // into the appropriate place in the list op. TfType itemArrayType; if (_IsGenericMetadataListOpType(fieldType, &itemArrayType)) { _SetupValue(schema.FindType(itemArrayType). GetAsToken().GetString(), context); } else { _SetupValue(schema.FindType(fieldDef.GetFallbackValue()). GetAsToken().GetString(), context); } } else { // Prepare to parse only the string representation of this metadata // value, since it's an unregistered field. context->values.StartRecordingString(); } } static void _GenericMetadataEnd(SdfSpecType specType, Sdf_TextParserContext *context) { const SdfSchema& schema = SdfSchema::GetInstance(); const SdfSchema::SpecDefinition &specDef = *schema.GetSpecDefinition(specType); if (specDef.IsMetadataField(context->genericMetadataKey)) { // Validate known fields before storing them const SdfSchema::FieldDefinition &fieldDef = *schema.GetFieldDefinition(context->genericMetadataKey); const TfType fieldType = fieldDef.GetFallbackValue().GetType(); if (_IsGenericMetadataListOpType(fieldType)) { if (!fieldDef.IsValidListValue(context->currentValue)) { Err(context, "invalid value for field \"%s\"", context->genericMetadataKey.GetText()); } else { _SetGenericMetadataListOpItems(fieldType, context); } } else { if (!fieldDef.IsValidValue(context->currentValue) || context->currentValue.IsEmpty()) { Err(context, "invalid value for field \"%s\"", context->genericMetadataKey.GetText()); } else { _SetField( context->path, context->genericMetadataKey, context->currentValue, context); } } } else if (specDef.IsValidField(context->genericMetadataKey)) { // Prevent the user from overwriting fields that aren't metadata Err(context, "\"%s\" is registered as a non-metadata field", context->genericMetadataKey.GetText()); } else { // Stuff unknown fields into a SdfUnregisteredValue so they can pass // through loading and saving unmodified VtValue value; if (context->currentValue.IsHolding()) { // If we parsed a dictionary, store it's actual value. Dictionaries // can be parsed fully because they contain type information. value = SdfUnregisteredValue(context->currentValue.Get()); } else { // Otherwise, we parsed a simple value or a shaped list of simple // values. We want to store the parsed string, but we need to // determine whether to unpack it into an SdfUnregisteredListOp // or to just store the string directly. auto getOldValue = [context]() { VtValue v; if (_HasField(context->path, context->genericMetadataKey, &v, context) && TF_VERIFY(v.IsHolding())) { v = v.UncheckedGet().GetValue(); } else { v = VtValue(); } return v; }; auto getRecordedStringAsUnregisteredValue = [context]() { std::string s = context->values.GetRecordedString(); if (s == "None") { return std::vector(); } // Put the entire string representation of this list into // a single SdfUnregisteredValue, but strip off the enclosing // brackets so that we don't write out two sets of brackets // when serializing out the list op. if (!s.empty() && s.front() == '[') { s.erase(0, 1); } if (!s.empty() && s.back() == ']') { s.pop_back(); } return std::vector( { SdfUnregisteredValue(s) }); }; VtValue oldValue = getOldValue(); if (context->listOpType == SdfListOpTypeExplicit) { // In this case, we can't determine whether the we've parsed // an explicit list op statement or a simple value. // We just store the recorded string directly, as that's the // simplest thing to do. value = SdfUnregisteredValue(context->values.GetRecordedString()); } else if (oldValue.IsEmpty() || oldValue.IsHolding()) { // In this case, we've parsed a list op statement so unpack // it into a list op unless we've already parsed something // for this field that *isn't* a list op. SdfUnregisteredValueListOp listOp = oldValue.GetWithDefault(); listOp.SetItems(getRecordedStringAsUnregisteredValue(), context->listOpType); value = SdfUnregisteredValue(listOp); } else { // If we've parsed a list op statement but have a non-list op // stored in this field, leave that value in place and ignore // the new value. We should only encounter this case if someone // hand-edited the layer in an unexpected or invalid way, so // just keeping the first value we find should be OK. } } if (!value.IsEmpty()) { _SetField(context->path, context->genericMetadataKey, value, context); } } context->values.Clear(); context->currentValue = VtValue(); } //-------------------------------------------------------------------- // The following are used to configure bison //-------------------------------------------------------------------- // Use this to enable generation of parser trace code. // Useful when debugging, but costly. To enable/disable, (un)comment: //#define SDF_PARSER_DEBUG_MODE #ifdef SDF_PARSER_DEBUG_MODE #define YYDEBUG 1 #endif // SDF_PARSER_DEBUG_MODE %} // Make this re-entrant %define api.pure %lex-param { yyscan_t yyscanner } %parse-param { Sdf_TextParserContext *context } //-------------------------------------------------------------------- // Define our tokens and types //-------------------------------------------------------------------- %token TOK_NL %token TOK_MAGIC %token TOK_SYNTAX_ERROR // Basic lexed data types %token TOK_ASSETREF %token TOK_PATHREF %token TOK_IDENTIFIER %token TOK_CXX_NAMESPACED_IDENTIFIER %token TOK_NAMESPACED_IDENTIFIER %token TOK_NUMBER %token TOK_STRING // Keywords // // NOTE! If you add any keywords or literal tokens here, be sure to add // them to the 'keyword' production rule below. %token TOK_ABSTRACT %token TOK_ADD %token TOK_APPEND %token TOK_CLASS %token TOK_CONFIG %token TOK_CONNECT %token TOK_CUSTOM %token TOK_CUSTOMDATA %token TOK_DEF %token TOK_DEFAULT %token TOK_DELETE %token TOK_DICTIONARY %token TOK_DISPLAYUNIT %token TOK_DOC %token TOK_INHERITS %token TOK_KIND %token TOK_NAMECHILDREN %token TOK_NONE %token TOK_OFFSET %token TOK_OVER %token TOK_PERMISSION %token TOK_PAYLOAD %token TOK_PREFIX_SUBSTITUTIONS %token TOK_SUFFIX_SUBSTITUTIONS %token TOK_PREPEND %token TOK_PROPERTIES %token TOK_REFERENCES %token TOK_RELOCATES %token TOK_REL %token TOK_RENAMES %token TOK_REORDER %token TOK_ROOTPRIMS %token TOK_SCALE %token TOK_SPECIALIZES %token TOK_SUBLAYERS %token TOK_SYMMETRYARGUMENTS %token TOK_SYMMETRYFUNCTION %token TOK_TIME_SAMPLES %token TOK_UNIFORM %token TOK_VARIANTS %token TOK_VARIANTSET %token TOK_VARIANTSETS %token TOK_VARYING %% // The first, root production rule menva_file: layer keyword: TOK_ABSTRACT | TOK_ADD | TOK_APPEND | TOK_CLASS | TOK_CONFIG | TOK_CONNECT | TOK_CUSTOM | TOK_CUSTOMDATA | TOK_DEF | TOK_DEFAULT | TOK_DELETE | TOK_DICTIONARY | TOK_DISPLAYUNIT | TOK_DOC | TOK_INHERITS | TOK_KIND | TOK_NAMECHILDREN | TOK_NONE | TOK_OFFSET | TOK_OVER | TOK_PAYLOAD | TOK_PERMISSION | TOK_PREFIX_SUBSTITUTIONS | TOK_SUFFIX_SUBSTITUTIONS | TOK_PREPEND | TOK_PROPERTIES | TOK_REFERENCES | TOK_RELOCATES | TOK_REL | TOK_RENAMES | TOK_REORDER | TOK_ROOTPRIMS | TOK_SCALE | TOK_SPECIALIZES | TOK_SUBLAYERS | TOK_SYMMETRYARGUMENTS | TOK_SYMMETRYFUNCTION | TOK_TIME_SAMPLES | TOK_UNIFORM | TOK_VARIANTS | TOK_VARIANTSET | TOK_VARIANTSETS | TOK_VARYING ; //-------------------------------------------------------------------- // Layer Structure //-------------------------------------------------------------------- layer_metadata_form: layer_metadata_opt | layer_metadata_opt prim_list newlines_opt { // Store the names of the root prims. _SetField( SdfPath::AbsoluteRootPath(), SdfChildrenKeys->PrimChildren, context->nameChildrenStack.back(), context); context->nameChildrenStack.pop_back(); } ; layer: TOK_MAGIC { _MatchMagicIdentifier($1, context); context->nameChildrenStack.push_back(std::vector()); _CreateSpec( SdfPath::AbsoluteRootPath(), SdfSpecTypePseudoRoot, context); ABORT_IF_ERROR(context->seenError); } layer_metadata_form ; layer_metadata_opt: newlines_opt | newlines_opt '(' layer_metadata_list_opt ')' newlines_opt { // Abort if error after layer metadata. ABORT_IF_ERROR(context->seenError); // If we're only reading metadata and we got here, // we're done. if (context->metadataOnly) YYACCEPT; } ; layer_metadata_list_opt: newlines_opt | newlines_opt layer_metadata_list stmtsep_opt ; layer_metadata_list: layer_metadata | layer_metadata_list stmtsep layer_metadata ; layer_metadata_key: identifier ; layer_metadata: TOK_STRING { _SetField( context->path, SdfFieldKeys->Comment, $1.Get(), context); } | layer_metadata_key { _GenericMetadataStart($1, SdfSpecTypePseudoRoot, context); } '=' metadata_value { _GenericMetadataEnd(SdfSpecTypePseudoRoot, context); } // Handling for generic metadata fields that use list ops. // Note that handling of the 'explicit' list op type is done // in the generic metadata clause above, since there is no // marker that the parser can use to recognize that case. | TOK_DELETE identifier { _GenericMetadataStart($2, SdfSpecTypePseudoRoot, context); context->listOpType = SdfListOpTypeDeleted; } '=' metadata_listop_list { _GenericMetadataEnd(SdfSpecTypePseudoRoot, context); } | TOK_ADD identifier { _GenericMetadataStart($2, SdfSpecTypePseudoRoot, context); context->listOpType = SdfListOpTypeAdded; } '=' metadata_listop_list { _GenericMetadataEnd(SdfSpecTypePseudoRoot, context); } | TOK_PREPEND identifier { _GenericMetadataStart($2, SdfSpecTypePseudoRoot, context); context->listOpType = SdfListOpTypePrepended; } '=' metadata_listop_list { _GenericMetadataEnd(SdfSpecTypePseudoRoot, context); } | TOK_APPEND identifier { _GenericMetadataStart($2, SdfSpecTypePseudoRoot, context); context->listOpType = SdfListOpTypeAppended; } '=' metadata_listop_list { _GenericMetadataEnd(SdfSpecTypePseudoRoot, context); } | TOK_REORDER identifier { _GenericMetadataStart($2, SdfSpecTypePseudoRoot, context); context->listOpType = SdfListOpTypeOrdered; } '=' metadata_listop_list { _GenericMetadataEnd(SdfSpecTypePseudoRoot, context); } // Not parsed with generic metadata because: key name changes from "doc" to // "documentation" | TOK_DOC '=' TOK_STRING { _SetField( context->path, SdfFieldKeys->Documentation, $3.Get(), context); } // Not parsed with generic metadata because: actually maps to two values // instead of one | TOK_SUBLAYERS '=' sublayer_list ; sublayer_list: '[' newlines_opt ']' | '[' newlines_opt sublayer_list_int listsep_opt ']' { _SetField( SdfPath::AbsoluteRootPath(), SdfFieldKeys->SubLayers, context->subLayerPaths, context); _SetField( SdfPath::AbsoluteRootPath(), SdfFieldKeys->SubLayerOffsets, context->subLayerOffsets, context); context->subLayerPaths.clear(); context->subLayerOffsets.clear(); } ; sublayer_list_int: sublayer_stmt | sublayer_list_int listsep sublayer_stmt ; sublayer_stmt: layer_ref layer_offset_opt { context->subLayerPaths.push_back(context->layerRefPath); context->subLayerOffsets.push_back(context->layerRefOffset); ABORT_IF_ERROR(context->seenError); } ; layer_ref: TOK_ASSETREF { context->layerRefPath = $1.Get(); context->layerRefOffset = SdfLayerOffset(); ABORT_IF_ERROR(context->seenError); } ; layer_offset_opt: /* empty */ | '(' layer_offset_int stmtsep_opt ')' ; layer_offset_int: layer_offset_stmt | layer_offset_int stmtsep layer_offset_stmt ; layer_offset_stmt: TOK_OFFSET '=' TOK_NUMBER { context->layerRefOffset.SetOffset( $3.Get() ); ABORT_IF_ERROR(context->seenError); } | TOK_SCALE '=' TOK_NUMBER { context->layerRefOffset.SetScale( $3.Get() ); ABORT_IF_ERROR(context->seenError); } ; prim_list: prim_stmt | prim_list newlines prim_stmt ; //-------------------------------------------------------------------- // Prim Structure //-------------------------------------------------------------------- prim_stmt: TOK_DEF { context->specifier = SdfSpecifierDef; context->typeName = TfToken(); } prim_stmt_int | TOK_DEF prim_type_name { context->specifier = SdfSpecifierDef; context->typeName = TfToken($2.Get()); } prim_stmt_int | TOK_CLASS { context->specifier = SdfSpecifierClass; context->typeName = TfToken(); } prim_stmt_int | TOK_CLASS prim_type_name { context->specifier = SdfSpecifierClass; context->typeName = TfToken($2.Get()); } prim_stmt_int | TOK_OVER { context->specifier = SdfSpecifierOver; context->typeName = TfToken(); } prim_stmt_int | TOK_OVER prim_type_name { context->specifier = SdfSpecifierOver; context->typeName = TfToken($2.Get()); } prim_stmt_int | TOK_REORDER TOK_ROOTPRIMS '=' name_list { _SetField( context->path, SdfFieldKeys->PrimOrder, context->nameVector, context); context->nameVector.clear(); } ; /* need support for fully qualified python identifiers */ prim_type_name : identifier { $$ = $1; } | prim_type_name '.' identifier { $$ = std::string( $1.Get() + '.' + $3.Get() ); } ; prim_stmt_int: TOK_STRING { TfToken name($1.Get()); if (!SdfPath::IsValidIdentifier(name)) { Err(context, "'%s' is not a valid prim name", name.GetText()); } context->path = context->path.AppendChild(name); if (_HasSpec(context->path, context)) { Err(context, "Duplicate prim '%s'", context->path.GetText()); } else { // Record the existence of this prim. _CreateSpec(context->path, SdfSpecTypePrim, context); // Add this prim to its parent's name children context->nameChildrenStack.back().push_back(name); } // Create our name children vector and properties vector. context->nameChildrenStack.push_back(std::vector()); context->propertiesStack.push_back(std::vector()); _SetField( context->path, SdfFieldKeys->Specifier, context->specifier, context); if (!context->typeName.IsEmpty()) _SetField( context->path, SdfFieldKeys->TypeName, context->typeName, context); } prim_metadata_opt '{' prim_contents_list_opt '}' { // Store the names of our children if (!context->nameChildrenStack.back().empty()) { _SetField( context->path, SdfChildrenKeys->PrimChildren, context->nameChildrenStack.back(), context); } // Store the names of our properties, if there are any if (!context->propertiesStack.back().empty()) { _SetField( context->path, SdfChildrenKeys->PropertyChildren, context->propertiesStack.back(), context); } context->nameChildrenStack.pop_back(); context->propertiesStack.pop_back(); context->path = context->path.GetParentPath(); // Abort after each prim if we hit an error. ABORT_IF_ERROR(context->seenError); } ; // Prim Metadata prim_metadata_opt: newlines_opt | newlines_opt '(' prim_metadata_list_opt ')' newlines_opt ; prim_metadata_list_opt: newlines_opt | newlines_opt prim_metadata_list stmtsep_opt ; prim_metadata_list: prim_metadata | prim_metadata_list stmtsep prim_metadata ; prim_metadata_key: identifier | TOK_CUSTOMDATA | TOK_SYMMETRYARGUMENTS ; prim_metadata: TOK_STRING { _SetField( context->path, SdfFieldKeys->Comment, $1.Get(), context); } | prim_metadata_key { _GenericMetadataStart($1, SdfSpecTypePrim, context); } '=' metadata_value { _GenericMetadataEnd(SdfSpecTypePrim, context); } // Handling for generic metadata fields that use list ops. // Note that handling of the 'explicit' list op type is done // in the generic metadata clause above, since there is no // marker that the parser can use to recognize that case. | TOK_DELETE identifier { _GenericMetadataStart($2, SdfSpecTypePrim, context); context->listOpType = SdfListOpTypeDeleted; } '=' metadata_listop_list { _GenericMetadataEnd(SdfSpecTypePrim, context); } | TOK_ADD identifier { _GenericMetadataStart($2, SdfSpecTypePrim, context); context->listOpType = SdfListOpTypeAdded; } '=' metadata_listop_list { _GenericMetadataEnd(SdfSpecTypePrim, context); } | TOK_PREPEND identifier { _GenericMetadataStart($2, SdfSpecTypePrim, context); context->listOpType = SdfListOpTypePrepended; } '=' metadata_listop_list { _GenericMetadataEnd(SdfSpecTypePrim, context); } | TOK_APPEND identifier { _GenericMetadataStart($2, SdfSpecTypePrim, context); context->listOpType = SdfListOpTypeAppended; } '=' metadata_listop_list { _GenericMetadataEnd(SdfSpecTypePrim, context); } | TOK_REORDER identifier { _GenericMetadataStart($2, SdfSpecTypePrim, context); context->listOpType = SdfListOpTypeOrdered; } '=' metadata_listop_list { _GenericMetadataEnd(SdfSpecTypePrim, context); } // Not parsed with generic metadata because: key name changes from "doc" to // "documentation" | TOK_DOC '=' TOK_STRING { _SetField( context->path, SdfFieldKeys->Documentation, $3.Get(), context); } // Not parsed with generic metadata because: value is parsed as a string // but stored as a token | TOK_KIND '=' TOK_STRING { _SetField( context->path, SdfFieldKeys->Kind, TfToken($3.Get()), context); } // Not parsed with generic metadata because: has shortcut names like // "public" instead of SdfPermissionPublic | TOK_PERMISSION '=' identifier { _SetField( context->path, SdfFieldKeys->Permission, _GetPermissionFromString($3.Get(), context), context); } // Not parsed with generic metadata because: SdfListOp is not supported | TOK_PAYLOAD { context->layerRefPath = std::string(); context->savedPath = SdfPath(); context->payloadParsingRefs.clear(); } '=' payload_list { _PrimSetPayloadListItems(SdfListOpTypeExplicit, context); } | TOK_DELETE TOK_PAYLOAD { context->layerRefPath = std::string(); context->savedPath = SdfPath(); context->payloadParsingRefs.clear(); } '=' payload_list { _PrimSetPayloadListItems(SdfListOpTypeDeleted, context); } | TOK_ADD TOK_PAYLOAD { context->layerRefPath = std::string(); context->savedPath = SdfPath(); context->payloadParsingRefs.clear(); } '=' payload_list { _PrimSetPayloadListItems(SdfListOpTypeAdded, context); } | TOK_PREPEND TOK_PAYLOAD { context->layerRefPath = std::string(); context->savedPath = SdfPath(); context->payloadParsingRefs.clear(); } '=' payload_list { _PrimSetPayloadListItems(SdfListOpTypePrepended, context); } | TOK_APPEND TOK_PAYLOAD { context->layerRefPath = std::string(); context->savedPath = SdfPath(); context->payloadParsingRefs.clear(); } '=' payload_list { _PrimSetPayloadListItems(SdfListOpTypeAppended, context); } | TOK_REORDER TOK_PAYLOAD { context->layerRefPath = std::string(); context->savedPath = SdfPath(); context->payloadParsingRefs.clear(); } '=' payload_list { _PrimSetPayloadListItems(SdfListOpTypeOrdered, context); } // Not parsed with generic metadata because: SdfListOp is not supported | TOK_INHERITS { context->inheritParsingTargetPaths.clear(); } '=' inherit_list { _PrimSetInheritListItems(SdfListOpTypeExplicit, context); } | TOK_DELETE TOK_INHERITS { context->inheritParsingTargetPaths.clear(); } '=' inherit_list { _PrimSetInheritListItems(SdfListOpTypeDeleted, context); } | TOK_ADD TOK_INHERITS { context->inheritParsingTargetPaths.clear(); } '=' inherit_list { _PrimSetInheritListItems(SdfListOpTypeAdded, context); } | TOK_PREPEND TOK_INHERITS { context->inheritParsingTargetPaths.clear(); } '=' inherit_list { _PrimSetInheritListItems(SdfListOpTypePrepended, context); } | TOK_APPEND TOK_INHERITS { context->inheritParsingTargetPaths.clear(); } '=' inherit_list { _PrimSetInheritListItems(SdfListOpTypeAppended, context); } | TOK_REORDER TOK_INHERITS { context->inheritParsingTargetPaths.clear(); } '=' inherit_list { _PrimSetInheritListItems(SdfListOpTypeOrdered, context); } // Not parsed with generic metadata because: SdfListOp is not supported | TOK_SPECIALIZES { context->specializesParsingTargetPaths.clear(); } '=' specializes_list { _PrimSetSpecializesListItems(SdfListOpTypeExplicit, context); } | TOK_DELETE TOK_SPECIALIZES { context->specializesParsingTargetPaths.clear(); } '=' specializes_list { _PrimSetSpecializesListItems(SdfListOpTypeDeleted, context); } | TOK_ADD TOK_SPECIALIZES { context->specializesParsingTargetPaths.clear(); } '=' specializes_list { _PrimSetSpecializesListItems(SdfListOpTypeAdded, context); } | TOK_PREPEND TOK_SPECIALIZES { context->specializesParsingTargetPaths.clear(); } '=' specializes_list { _PrimSetSpecializesListItems(SdfListOpTypePrepended, context); } | TOK_APPEND TOK_SPECIALIZES { context->specializesParsingTargetPaths.clear(); } '=' specializes_list { _PrimSetSpecializesListItems(SdfListOpTypeAppended, context); } | TOK_REORDER TOK_SPECIALIZES { context->specializesParsingTargetPaths.clear(); } '=' specializes_list { _PrimSetSpecializesListItems(SdfListOpTypeOrdered, context); } // Not parsed with generic metadata because: SdfListOp is not supported | TOK_REFERENCES { context->layerRefPath = std::string(); context->savedPath = SdfPath(); context->referenceParsingRefs.clear(); } '=' reference_list { _PrimSetReferenceListItems(SdfListOpTypeExplicit, context); } | TOK_DELETE TOK_REFERENCES { context->layerRefPath = std::string(); context->savedPath = SdfPath(); context->referenceParsingRefs.clear(); } '=' reference_list { _PrimSetReferenceListItems(SdfListOpTypeDeleted, context); } | TOK_ADD TOK_REFERENCES { context->layerRefPath = std::string(); context->savedPath = SdfPath(); context->referenceParsingRefs.clear(); } '=' reference_list { _PrimSetReferenceListItems(SdfListOpTypeAdded, context); } | TOK_PREPEND TOK_REFERENCES { context->layerRefPath = std::string(); context->savedPath = SdfPath(); context->referenceParsingRefs.clear(); } '=' reference_list { _PrimSetReferenceListItems(SdfListOpTypePrepended, context); } | TOK_APPEND TOK_REFERENCES { context->layerRefPath = std::string(); context->savedPath = SdfPath(); context->referenceParsingRefs.clear(); } '=' reference_list { _PrimSetReferenceListItems(SdfListOpTypeAppended, context); } | TOK_REORDER TOK_REFERENCES { context->layerRefPath = std::string(); context->savedPath = SdfPath(); context->referenceParsingRefs.clear(); } '=' reference_list { _PrimSetReferenceListItems(SdfListOpTypeOrdered, context); } // Not parsed with generic metadata because: uses special Python-like // dictionary syntax with paths | TOK_RELOCATES '=' relocates_map { _SetField( context->path, SdfFieldKeys->Relocates, context->relocatesParsingMap, context); context->relocatesParsingMap.clear(); } // Not parsed with generic metadata because: multiple definitions are // merged into one dictionary instead of overwriting previous definitions | TOK_VARIANTS '=' typed_dictionary { _PrimSetVariantSelection(context); } // Not parsed with generic metadata because: SdfListOp is not supported | TOK_VARIANTSETS '=' name_list { _PrimSetVariantSetNamesListItems(SdfListOpTypeExplicit, context); context->nameVector.clear(); } | TOK_DELETE TOK_VARIANTSETS '=' name_list { _PrimSetVariantSetNamesListItems(SdfListOpTypeDeleted, context); context->nameVector.clear(); } | TOK_ADD TOK_VARIANTSETS '=' name_list { _PrimSetVariantSetNamesListItems(SdfListOpTypeAdded, context); context->nameVector.clear(); } | TOK_PREPEND TOK_VARIANTSETS '=' name_list { _PrimSetVariantSetNamesListItems(SdfListOpTypePrepended, context); context->nameVector.clear(); } | TOK_APPEND TOK_VARIANTSETS '=' name_list { _PrimSetVariantSetNamesListItems(SdfListOpTypeAppended, context); context->nameVector.clear(); } | TOK_REORDER TOK_VARIANTSETS '=' name_list { _PrimSetVariantSetNamesListItems(SdfListOpTypeOrdered, context); context->nameVector.clear(); } // Not parsed with generic metadata because: allows assignment to an empty // string by omitting the value | TOK_SYMMETRYFUNCTION '=' identifier { _SetField( context->path, SdfFieldKeys->SymmetryFunction, TfToken($3.Get()), context); } | TOK_SYMMETRYFUNCTION '=' { _SetField( context->path, SdfFieldKeys->SymmetryFunction, TfToken(), context); } // Not parsed with generic metadata because: uses special Python-like // dictionary syntax | TOK_PREFIX_SUBSTITUTIONS '=' string_dictionary { _SetField( context->path, SdfFieldKeys->PrefixSubstitutions, context->currentDictionaries[0], context); context->currentDictionaries[0].clear(); } // Not parsed with generic metadata because: uses special Python-like // dictionary syntax | TOK_SUFFIX_SUBSTITUTIONS '=' string_dictionary { _SetField( context->path, SdfFieldKeys->SuffixSubstitutions, context->currentDictionaries[0], context); context->currentDictionaries[0].clear(); } ; payload_list: TOK_NONE | payload_list_item | '[' newlines_opt ']' | '[' newlines_opt payload_list_int listsep_opt ']' ; payload_list_int: payload_list_item | payload_list_int listsep payload_list_item ; payload_list_item: layer_ref prim_path_opt payload_params_opt { if (context->layerRefPath.empty()) { Err(context, "Payload asset path must not be empty. If this " "is intended to be an internal payload, remove the " "'@' delimiters."); } SdfPayload payload(context->layerRefPath, context->savedPath, context->layerRefOffset); context->payloadParsingRefs.push_back(payload); } | TOK_PATHREF { // Internal payloads do not begin with an asset path so there's // no layer_ref rule, but we need to make sure we reset state the // so we don't pick up data from a previously-parsed payload. context->layerRefPath.clear(); context->layerRefOffset = SdfLayerOffset(); ABORT_IF_ERROR(context->seenError); } payload_params_opt { if (!$1.Get().empty()) { _PathSetPrim($1, context); } else { context->savedPath = SdfPath::EmptyPath(); } SdfPayload payload(std::string(), context->savedPath, context->layerRefOffset); context->payloadParsingRefs.push_back(payload); } ; payload_params_opt: /* empty */ | '(' newlines_opt ')' | '(' newlines_opt payload_params_int stmtsep_opt ')' ; payload_params_int: payload_params_item | payload_params_int stmtsep payload_params_item ; payload_params_item: layer_offset_stmt ; reference_list: TOK_NONE | reference_list_item | '[' newlines_opt ']' | '[' newlines_opt reference_list_int listsep_opt ']' ; reference_list_int: reference_list_item | reference_list_int listsep reference_list_item ; reference_list_item: layer_ref prim_path_opt reference_params_opt { if (context->layerRefPath.empty()) { Err(context, "Reference asset path must not be empty. If this " "is intended to be an internal reference, remove the " "'@' delimiters."); } SdfReference ref(context->layerRefPath, context->savedPath, context->layerRefOffset); ref.SwapCustomData(context->currentDictionaries[0]); context->referenceParsingRefs.push_back(ref); } | TOK_PATHREF { // Internal references do not begin with an asset path so there's // no layer_ref rule, but we need to make sure we reset state the // so we don't pick up data from a previously-parsed reference. context->layerRefPath.clear(); context->layerRefOffset = SdfLayerOffset(); ABORT_IF_ERROR(context->seenError); } reference_params_opt { if (!$1.Get().empty()) { _PathSetPrim($1, context); } else { context->savedPath = SdfPath::EmptyPath(); } SdfReference ref(std::string(), context->savedPath, context->layerRefOffset); ref.SwapCustomData(context->currentDictionaries[0]); context->referenceParsingRefs.push_back(ref); } ; reference_params_opt: /* empty */ | '(' newlines_opt ')' | '(' newlines_opt reference_params_int stmtsep_opt ')' ; reference_params_int: reference_params_item | reference_params_int stmtsep reference_params_item ; reference_params_item: layer_offset_stmt | TOK_CUSTOMDATA '=' typed_dictionary ; inherit_list: TOK_NONE | inherit_list_item | '[' newlines_opt ']' | '[' newlines_opt inherit_list_int listsep_opt ']' ; inherit_list_int: inherit_list_item | inherit_list_int listsep inherit_list_item ; inherit_list_item: prim_path { _InheritAppendPath(context); } ; specializes_list: TOK_NONE | specializes_list_item | '[' newlines_opt ']' | '[' newlines_opt specializes_list_int listsep_opt ']' ; specializes_list_int: specializes_list_item | specializes_list_int listsep specializes_list_item ; specializes_list_item: prim_path { _SpecializesAppendPath(context); } ; relocates_map: '{' newlines_opt relocates_stmt_list_opt '}' ; relocates_stmt_list_opt: /* empty */ | relocates_stmt_list listsep_opt ; relocates_stmt_list: relocates_stmt | relocates_stmt_list listsep relocates_stmt ; relocates_stmt: TOK_PATHREF ':' TOK_PATHREF { _RelocatesAdd($1, $3, context); } ; name_list: name_list_item | '[' newlines_opt name_list_int listsep_opt ']' ; name_list_int: name_list_item | name_list_int listsep name_list_item ; name_list_item: TOK_STRING { context->nameVector.push_back(TfToken($1.Get())); } ; // Prim Contents prim_contents_list_opt: newlines_opt | newlines_opt prim_contents_list ; prim_contents_list: prim_contents_list_item | prim_contents_list prim_contents_list_item ; prim_contents_list_item: prim_property stmtsep {} | prim_child_order_stmt stmtsep {} | prim_property_order_stmt stmtsep {} | prim_stmt newlines | variantset_stmt newlines ; variantset_stmt: TOK_VARIANTSET TOK_STRING { const std::string name = $2.Get(); ERROR_IF_NOT_ALLOWED(context, SdfSchema::IsValidVariantIdentifier(name)); context->currentVariantSetNames.push_back( name ); context->currentVariantNames.push_back( std::vector() ); context->path = context->path.AppendVariantSelection(name, ""); } '=' newlines_opt '{' newlines_opt variant_list '}' { SdfPath variantSetPath = context->path; context->path = context->path.GetParentPath(); // Create this VariantSetSpec if it does not already exist. if (!_HasSpec(variantSetPath, context)) { _CreateSpec(variantSetPath, SdfSpecTypeVariantSet, context); // Add the name of this variant set to the VariantSets field _AppendVectorItem(SdfChildrenKeys->VariantSetChildren, TfToken(context->currentVariantSetNames.back()), context); } // Author the variant set's variants _SetField( variantSetPath, SdfChildrenKeys->VariantChildren, TfToTokenVector(context->currentVariantNames.back()), context); context->currentVariantSetNames.pop_back(); context->currentVariantNames.pop_back(); } ; variant_list: variant_stmt | variant_list variant_stmt ; variant_stmt: TOK_STRING { const std::string variantName = $1.Get(); ERROR_IF_NOT_ALLOWED( context, SdfSchema::IsValidVariantIdentifier(variantName)); context->currentVariantNames.back().push_back(variantName); // A variant is basically like a new pseudo-root, so we need to push // a new item onto our name children stack to store prims defined // within this variant. context->nameChildrenStack.push_back(std::vector()); context->propertiesStack.push_back(std::vector()); std::string variantSetName = context->currentVariantSetNames.back(); context->path = context->path.GetParentPath() .AppendVariantSelection(variantSetName, variantName); _CreateSpec(context->path, SdfSpecTypeVariant, context); } prim_metadata_opt '{' prim_contents_list_opt '}' newlines_opt { // Store the names of the prims and properties defined in this variant. if (!context->nameChildrenStack.back().empty()) { _SetField( context->path, SdfChildrenKeys->PrimChildren, context->nameChildrenStack.back(), context); } if (!context->propertiesStack.back().empty()) { _SetField( context->path, SdfChildrenKeys->PropertyChildren, context->propertiesStack.back(), context); } context->nameChildrenStack.pop_back(); context->propertiesStack.pop_back(); std::string variantSet = context->path.GetVariantSelection().first; context->path = context->path.GetParentPath().AppendVariantSelection(variantSet, ""); } ; prim_child_order_stmt: TOK_REORDER TOK_NAMECHILDREN '=' name_list { _SetField( context->path, SdfFieldKeys->PrimOrder, context->nameVector, context); context->nameVector.clear(); } ; prim_property_order_stmt: TOK_REORDER TOK_PROPERTIES '=' name_list { _SetField( context->path, SdfFieldKeys->PropertyOrder, context->nameVector, context); context->nameVector.clear(); } ; //-------------------------------------------------------------------- // Property Structure //-------------------------------------------------------------------- prim_property: prim_attribute | prim_relationship ; //-------------------------------------------------------------------- // Attribute Structure //-------------------------------------------------------------------- prim_attr_variability : TOK_UNIFORM { context->variability = VtValue(SdfVariabilityUniform); } | TOK_CONFIG { // Convert legacy "config" variability to SdfVariabilityUniform. // This value was never officially used in USD but we handle // this just in case the value was written out. context->variability = VtValue(SdfVariabilityUniform); } ; prim_attr_qualifiers: prim_attr_variability { context->assoc = VtValue(); } ; prim_attr_type: identifier { _SetupValue($1.Get(), context); } | identifier '[' ']' { _SetupValue(std::string($1.Get() + "[]"), context); } ; prim_attribute_full_type : prim_attr_type { context->variability = VtValue(); context->custom = false; } | prim_attr_qualifiers prim_attr_type { context->custom = false; } ; prim_attribute_default: prim_attribute_full_type namespaced_name { _PrimInitAttribute($2, context); if (!context->values.valueTypeIsValid) { context->values.StartRecordingString(); } } attribute_assignment_opt { if (!context->values.valueTypeIsValid) { context->values.StopRecordingString(); } } attribute_metadata_list_opt { context->path = context->path.GetParentPath(); } ; prim_attribute_fallback: TOK_CUSTOM prim_attribute_full_type namespaced_name { context->custom = true; _PrimInitAttribute($3, context); if (!context->values.valueTypeIsValid) { context->values.StartRecordingString(); } } attribute_assignment_opt { if (!context->values.valueTypeIsValid) { context->values.StopRecordingString(); } } attribute_metadata_list_opt { context->path = context->path.GetParentPath(); } ; prim_attribute_connect : prim_attribute_full_type namespaced_name '.' TOK_CONNECT '=' { _PrimInitAttribute($2, context); context->connParsingTargetPaths.clear(); context->connParsingAllowConnectionData = true; } connect_rhs { _AttributeSetConnectionTargetsList(SdfListOpTypeExplicit, context); context->path = context->path.GetParentPath(); } | TOK_ADD prim_attribute_full_type namespaced_name '.' TOK_CONNECT '=' { _PrimInitAttribute($3, context); context->connParsingTargetPaths.clear(); context->connParsingAllowConnectionData = true; } connect_rhs { _AttributeSetConnectionTargetsList(SdfListOpTypeAdded, context); context->path = context->path.GetParentPath(); } | TOK_PREPEND prim_attribute_full_type namespaced_name '.' TOK_CONNECT '=' { _PrimInitAttribute($3, context); context->connParsingTargetPaths.clear(); context->connParsingAllowConnectionData = true; } connect_rhs { _AttributeSetConnectionTargetsList(SdfListOpTypePrepended, context); context->path = context->path.GetParentPath(); } | TOK_APPEND prim_attribute_full_type namespaced_name '.' TOK_CONNECT '=' { _PrimInitAttribute($3, context); context->connParsingTargetPaths.clear(); context->connParsingAllowConnectionData = true; } connect_rhs { _AttributeSetConnectionTargetsList(SdfListOpTypeAppended, context); context->path = context->path.GetParentPath(); } | TOK_DELETE prim_attribute_full_type namespaced_name '.' TOK_CONNECT '=' { _PrimInitAttribute($3, context); context->connParsingTargetPaths.clear(); context->connParsingAllowConnectionData = false; } connect_rhs { _AttributeSetConnectionTargetsList(SdfListOpTypeDeleted, context); context->path = context->path.GetParentPath(); } | TOK_REORDER prim_attribute_full_type namespaced_name '.' TOK_CONNECT '=' { _PrimInitAttribute($3, context); context->connParsingTargetPaths.clear(); context->connParsingAllowConnectionData = false; } connect_rhs { _AttributeSetConnectionTargetsList(SdfListOpTypeOrdered, context); context->path = context->path.GetParentPath(); } ; prim_attribute_time_samples: prim_attribute_full_type namespaced_name '.' TOK_TIME_SAMPLES '=' { _PrimInitAttribute($2, context); } time_samples_rhs { _SetField( context->path, SdfFieldKeys->TimeSamples, context->timeSamples, context); context->path = context->path.GetParentPath(); // pop attr } ; prim_attribute: prim_attribute_fallback | prim_attribute_default | prim_attribute_connect | prim_attribute_time_samples ; //-------------------------------------------------------------------- // Attribute connections //-------------------------------------------------------------------- connect_rhs: TOK_NONE | connect_item | '[' newlines_opt ']' | '[' newlines_opt connect_list listsep_opt ']' ; connect_list: connect_item | connect_list listsep connect_item ; connect_item: prim_or_property_scene_path { _AttributeAppendConnectionPath(context); } ; //-------------------------------------------------------------------- // Time sample maps //-------------------------------------------------------------------- time_samples_rhs: '{' { context->timeSamples = SdfTimeSampleMap(); } newlines_opt time_sample_list '}' ; time_sample_list: /* empty */ | time_sample_list_int listsep_opt ; time_sample_list_int: time_sample | time_sample_list_int listsep time_sample ; time_sample: extended_number ':' { context->timeSampleTime = $1.Get(); } typed_value { context->timeSamples[ context->timeSampleTime ] = context->currentValue; } | extended_number ':' TOK_NONE { context->timeSampleTime = $1.Get(); context->timeSamples[ context->timeSampleTime ] = VtValue(SdfValueBlock()); } ; //-------------------------------------------------------------------- // Attribute Metadata //-------------------------------------------------------------------- attribute_metadata_list_opt: /* empty */ | '(' newlines_opt ')' | '(' newlines_opt attribute_metadata_list stmtsep_opt ')' ; attribute_metadata_list: attribute_metadata | attribute_metadata_list stmtsep attribute_metadata ; attribute_metadata_key: identifier | TOK_CUSTOMDATA | TOK_SYMMETRYARGUMENTS ; attribute_metadata: TOK_STRING { _SetField( context->path, SdfFieldKeys->Comment, $1.Get(), context); } | attribute_metadata_key { _GenericMetadataStart($1, SdfSpecTypeAttribute, context); } '=' metadata_value { _GenericMetadataEnd(SdfSpecTypeAttribute, context); } // Handling for generic metadata fields that use list ops. // Note that handling of the 'explicit' list op type is done // in the generic metadata clause above, since there is no // marker that the parser can use to recognize that case. | TOK_DELETE identifier { _GenericMetadataStart($2, SdfSpecTypeAttribute, context); context->listOpType = SdfListOpTypeDeleted; } '=' metadata_listop_list { _GenericMetadataEnd(SdfSpecTypeAttribute, context); } | TOK_ADD identifier { _GenericMetadataStart($2, SdfSpecTypeAttribute, context); context->listOpType = SdfListOpTypeAdded; } '=' metadata_listop_list { _GenericMetadataEnd(SdfSpecTypeAttribute, context); } | TOK_PREPEND identifier { _GenericMetadataStart($2, SdfSpecTypeAttribute, context); context->listOpType = SdfListOpTypePrepended; } '=' metadata_listop_list { _GenericMetadataEnd(SdfSpecTypeAttribute, context); } | TOK_APPEND identifier { _GenericMetadataStart($2, SdfSpecTypeAttribute, context); context->listOpType = SdfListOpTypeAppended; } '=' metadata_listop_list { _GenericMetadataEnd(SdfSpecTypeAttribute, context); } | TOK_REORDER identifier { _GenericMetadataStart($2, SdfSpecTypeAttribute, context); context->listOpType = SdfListOpTypeOrdered; } '=' metadata_listop_list { _GenericMetadataEnd(SdfSpecTypeAttribute, context); } // Not parsed with generic metadata because: key name changes from "doc" to // "documentation" | TOK_DOC '=' TOK_STRING { _SetField( context->path, SdfFieldKeys->Documentation, $3.Get(), context); } // Not parsed with generic metadata because: has shortcut names like // "public" instead of SdfPermissionPublic | TOK_PERMISSION '=' identifier { _SetField( context->path, SdfFieldKeys->Permission, _GetPermissionFromString($3.Get(), context), context); } // Not parsed with generic metadata because: parsed as a TfEnum | TOK_DISPLAYUNIT '=' identifier { _SetField( context->path, SdfFieldKeys->DisplayUnit, _GetDisplayUnitFromString($3.Get(), context), context); } // Not parsed with generic metadata because: allows assignment to an empty // string by omitting the value | TOK_SYMMETRYFUNCTION '=' identifier { _SetField( context->path, SdfFieldKeys->SymmetryFunction, TfToken($3.Get()), context); } | TOK_SYMMETRYFUNCTION '=' { _SetField( context->path, SdfFieldKeys->SymmetryFunction, TfToken(), context); } ; attribute_assignment_opt: /* empty */ | '=' attribute_value ; attribute_value: typed_value { _SetField( context->path, SdfFieldKeys->Default, context->currentValue, context); } | TOK_NONE { _SetField( context->path, SdfFieldKeys->Default, SdfValueBlock(), context); } ; //------------------------ // Dictionary structure //------------------------ typed_dictionary: '{' { _DictionaryBegin(context); } newlines_opt typed_dictionary_list_opt '}' { _DictionaryEnd(context); } ; typed_dictionary_list_opt: /* empty */ | typed_dictionary_list stmtsep_opt ; typed_dictionary_list: typed_dictionary_element | typed_dictionary_list stmtsep typed_dictionary_element ; typed_dictionary_element: dictionary_value_type dictionary_key '=' typed_value { _DictionaryInsertValue($2, context); } | TOK_DICTIONARY dictionary_key '=' typed_dictionary { _DictionaryInsertDictionary($2, context); } ; // dictionary keys should be able to strings that are numbers, like "1" or "20", // so we need to add TOK_STRING in addition to name. dictionary_key: TOK_STRING | name ; dictionary_value_type: dictionary_value_scalar_type | dictionary_value_shaped_type ; dictionary_value_scalar_type: identifier { _DictionaryInitScalarFactory($1, context); } ; dictionary_value_shaped_type: identifier '[' ']' { _DictionaryInitShapedFactory($1, context); } ; //------------------------ // String-to-string dictionary //------------------------ string_dictionary: '{' { _DictionaryBegin(context); } newlines_opt string_dictionary_list_opt '}' { _DictionaryEnd(context); } ; string_dictionary_list_opt: /* empty */ | string_dictionary_list listsep_opt ; string_dictionary_list: string_dictionary_element | string_dictionary_list listsep string_dictionary_element ; string_dictionary_element: TOK_STRING ':' TOK_STRING { _DictionaryInitScalarFactory(Value(std::string("string")), context); _ValueAppendAtomic($3, context); _ValueSetAtomic(context); _DictionaryInsertValue($1, context); } ; //---------------------- // List operation value //---------------------- metadata_listop_list: TOK_NONE { context->currentValue = VtValue(); if (context->values.IsRecordingString()) { context->values.SetRecordedString("None"); } } | typed_value_list { _ValueSetList(context); } ; //-------------------------------------------------------------------- // Value structure: scalar, shaped, and tuple values //-------------------------------------------------------------------- metadata_value: typed_dictionary { context->currentValue.Swap(context->currentDictionaries[0]); context->currentDictionaries[0].clear(); } | typed_value | TOK_NONE { // This is only here to allow 'None' metadata values for // an explicit list operation on an SdfListOp-valued field. // We'll reject this value for any other metadata field // in _GenericMetadataEnd. context->currentValue = VtValue(); if (context->values.IsRecordingString()) { context->values.SetRecordedString("None"); } } ; typed_value: typed_value_atomic { _ValueSetAtomic(context); } | typed_value_tuple { _ValueSetTuple(context); } | typed_value_list { _ValueSetList(context); } | '[' ']' { // Set the recorded string on the ParserValueContext. Normally // 'values' is able to keep track of the parsed string, but in this // case it doesn't get the BeginList() and EndList() calls so the // recorded string would have been "". We want "[]" instead. if (context->values.IsRecordingString()) { context->values.SetRecordedString("[]"); } _ValueSetShaped(context); } | TOK_PATHREF { _ValueSetCurrentToSdfPath($1, context); } ; typed_value_atomic: TOK_NUMBER { _ValueAppendAtomic($1, context); } | TOK_STRING { _ValueAppendAtomic($1, context); } | identifier { // The ParserValueContext needs identifiers to be stored as TfToken // instead of std::string to be able to distinguish between them. _ValueAppendAtomic(TfToken($1.Get()), context); } | TOK_ASSETREF { // The ParserValueContext needs asset paths to be stored as // SdfAssetPath instead of std::string to be able to distinguish // between them _ValueAppendAtomic(SdfAssetPath($1.Get()), context); } ; //---------------------- // Value lists, aka shaped values //---------------------- typed_value_list: '[' { context->values.BeginList(); } typed_value_list_int ']' { context->values.EndList(); } ; typed_value_list_int: newlines_opt typed_value_list_items listsep_opt ; typed_value_list_items: typed_value_list_item | typed_value_list_items listsep typed_value_list_item ; typed_value_list_item: typed_value_atomic | typed_value_list | typed_value_tuple ; //---------------------- // Value tuples //---------------------- typed_value_tuple: '(' { context->values.BeginTuple(); } typed_value_tuple_int ')' { context->values.EndTuple(); } ; typed_value_tuple_int: newlines_opt typed_value_tuple_items listsep_opt ; typed_value_tuple_items: typed_value_tuple_item | typed_value_tuple_items listsep typed_value_tuple_item ; typed_value_tuple_item: typed_value_atomic | typed_value_tuple ; //-------------------------------------------------------------------- // Relationships //-------------------------------------------------------------------- prim_relationship_type : TOK_REL { context->custom = false; context->variability = VtValue(SdfVariabilityUniform); } | TOK_CUSTOM TOK_REL { context->custom = true; context->variability = VtValue(SdfVariabilityUniform); } | TOK_CUSTOM TOK_VARYING TOK_REL { context->custom = true; context->variability = VtValue(SdfVariabilityVarying); } | TOK_VARYING TOK_REL { context->custom = false; context->variability = VtValue(SdfVariabilityVarying); } ; prim_relationship_time_samples: prim_relationship_type namespaced_name '.' TOK_TIME_SAMPLES '=' { _PrimInitRelationship($2, context); } time_samples_rhs { _SetField( context->path, SdfFieldKeys->TimeSamples, context->timeSamples, context); _PrimEndRelationship(context); } ; prim_relationship_default: prim_relationship_type namespaced_name '.' TOK_DEFAULT '=' TOK_PATHREF { _PrimInitRelationship($2, context); // If path is empty, use default c'tor to construct empty path. // XXX: 08/04/08 Would be nice if SdfPath would allow // SdfPath("") without throwing a warning. std::string pathString = $6.Get(); SdfPath path = pathString.empty() ? SdfPath() : SdfPath(pathString); _SetField(context->path, SdfFieldKeys->Default, path, context); _PrimEndRelationship(context); } ; prim_relationship: prim_relationship_type namespaced_name { _PrimInitRelationship($2, context); context->relParsingAllowTargetData = true; } relationship_assignment_opt relationship_metadata_list_opt { _RelationshipSetTargetsList(SdfListOpTypeExplicit, context); _PrimEndRelationship(context); } | TOK_DELETE prim_relationship_type namespaced_name { _PrimInitRelationship($3, context); } relationship_assignment_opt { _RelationshipSetTargetsList(SdfListOpTypeDeleted, context); _PrimEndRelationship(context); } | TOK_ADD prim_relationship_type namespaced_name { _PrimInitRelationship($3, context); context->relParsingAllowTargetData = true; } relationship_assignment_opt { _RelationshipSetTargetsList(SdfListOpTypeAdded, context); _PrimEndRelationship(context); } | TOK_PREPEND prim_relationship_type namespaced_name { _PrimInitRelationship($3, context); context->relParsingAllowTargetData = true; } relationship_assignment_opt { _RelationshipSetTargetsList(SdfListOpTypePrepended, context); _PrimEndRelationship(context); } | TOK_APPEND prim_relationship_type namespaced_name { _PrimInitRelationship($3, context); context->relParsingAllowTargetData = true; } relationship_assignment_opt { _RelationshipSetTargetsList(SdfListOpTypeAppended, context); _PrimEndRelationship(context); } | TOK_REORDER prim_relationship_type namespaced_name { _PrimInitRelationship($3, context); } relationship_assignment_opt { _RelationshipSetTargetsList(SdfListOpTypeOrdered, context); _PrimEndRelationship(context); } | prim_relationship_type namespaced_name '[' TOK_PATHREF ']' { _PrimInitRelationship($2, context); context->relParsingAllowTargetData = true; _RelationshipAppendTargetPath($4, context); _RelationshipInitTarget(context->relParsingTargetPaths->back(), context); } | prim_relationship_time_samples | prim_relationship_default ; relationship_metadata_list_opt: /* empty */ | '(' newlines_opt ')' | '(' newlines_opt relationship_metadata_list stmtsep_opt ')' ; relationship_metadata_list: relationship_metadata | relationship_metadata_list stmtsep relationship_metadata ; relationship_metadata_key: identifier | TOK_CUSTOMDATA | TOK_SYMMETRYARGUMENTS ; relationship_metadata: TOK_STRING { _SetField( context->path, SdfFieldKeys->Comment, $1.Get(), context); } | relationship_metadata_key { _GenericMetadataStart($1, SdfSpecTypeRelationship, context); } '=' metadata_value { _GenericMetadataEnd(SdfSpecTypeRelationship, context); } // Handling for generic metadata fields that use list ops. // Note that handling of the 'explicit' list op type is done // in the generic metadata clause above, since there is no // marker that the parser can use to recognize that case. | TOK_DELETE identifier { _GenericMetadataStart($2, SdfSpecTypeRelationship, context); context->listOpType = SdfListOpTypeDeleted; } '=' metadata_listop_list { _GenericMetadataEnd(SdfSpecTypeRelationship, context); } | TOK_ADD identifier { _GenericMetadataStart($2, SdfSpecTypeRelationship, context); context->listOpType = SdfListOpTypeAdded; } '=' metadata_listop_list { _GenericMetadataEnd(SdfSpecTypeRelationship, context); } | TOK_PREPEND identifier { _GenericMetadataStart($2, SdfSpecTypeRelationship, context); context->listOpType = SdfListOpTypePrepended; } '=' metadata_listop_list { _GenericMetadataEnd(SdfSpecTypeRelationship, context); } | TOK_APPEND identifier { _GenericMetadataStart($2, SdfSpecTypeRelationship, context); context->listOpType = SdfListOpTypeAppended; } '=' metadata_listop_list { _GenericMetadataEnd(SdfSpecTypeRelationship, context); } | TOK_REORDER identifier { _GenericMetadataStart($2, SdfSpecTypeRelationship, context); context->listOpType = SdfListOpTypeOrdered; } '=' metadata_listop_list { _GenericMetadataEnd(SdfSpecTypeRelationship, context); } // Not parsed with generic metadata because: key name changes from "doc" to // "documentation" | TOK_DOC '=' TOK_STRING { _SetField( context->path, SdfFieldKeys->Documentation, $3.Get(), context); } // Not parsed with generic metadata because: has shortcut names like // "public" instead of SdfPermissionPublic | TOK_PERMISSION '=' identifier { _SetField( context->path, SdfFieldKeys->Permission, _GetPermissionFromString($3.Get(), context), context); } // Not parsed with generic metadata because: allows assignment to an empty // string by omitting the value | TOK_SYMMETRYFUNCTION '=' identifier { _SetField( context->path, SdfFieldKeys->SymmetryFunction, TfToken($3.Get()), context); } | TOK_SYMMETRYFUNCTION '=' { _SetField( context->path, SdfFieldKeys->SymmetryFunction, TfToken(), context); } ; relationship_assignment_opt: /* empty */ | '=' relationship_rhs ; relationship_rhs: relationship_target | TOK_NONE { context->relParsingTargetPaths = SdfPathVector(); } | '[' newlines_opt ']' { context->relParsingTargetPaths = SdfPathVector(); } | '[' newlines_opt relationship_target_list listsep_opt ']' ; relationship_target_list: relationship_target | relationship_target_list listsep relationship_target ; relationship_target: TOK_PATHREF { _RelationshipAppendTargetPath($1, context); } ; //-------------------------------------------------------------------- // Syntactic utilities //-------------------------------------------------------------------- prim_path_opt: /* empty */ { context->savedPath = SdfPath(); } | prim_path ; prim_path: TOK_PATHREF { _PathSetPrim($1, context); } ; prim_or_property_scene_path: TOK_PATHREF { _PathSetPrimOrPropertyScenePath($1, context); } ; // A generic name, used to name prims, etc. // // We accept C/Python identifiers, C++ namespaced identifiers, and our // full set of keywords to ensure that we don't prevent people from using // keywords for names where it would not be ambiguous. name: identifier | keyword ; // An optionally namespaced name, used to name attributes and relationships. // We do not support C++ namespaced names. namespaced_name: TOK_IDENTIFIER | TOK_NAMESPACED_IDENTIFIER | keyword ; // A generic name including C++ namespaced names but not keywords. identifier: TOK_IDENTIFIER | TOK_CXX_NAMESPACED_IDENTIFIER ; // A number, inf, -inf or nan, all as double. extended_number: TOK_NUMBER | TOK_IDENTIFIER { $$ = $1; } ; stmtsep_opt: /* empty */ | stmtsep ; stmtsep: ';' newlines_opt | newlines ; listsep_opt: newlines_opt | listsep ; listsep: ',' newlines_opt ; newlines_opt: /* empty */ | newlines ; newlines: TOK_NL | newlines TOK_NL ; %% //-------------------------------------------------------------------- // textFileFormatYyerror //-------------------------------------------------------------------- void textFileFormatYyerror(Sdf_TextParserContext *context, const char *msg) { const std::string nextToken(textFileFormatYyget_text(context->scanner), textFileFormatYyget_leng(context->scanner)); const bool isNewlineToken = (nextToken.length() == 1 && nextToken[0] == '\n'); int errLineNumber = context->menvaLineNo; // By this time, menvaLineNo has already been updated to account for // nextToken. So, if nextToken is a newline, the error really occurred on // the previous line. if (isNewlineToken) { errLineNumber -= 1; } std::string s = TfStringPrintf( "%s%s in <%s> on line %i", msg, isNewlineToken ? "" : TfStringPrintf(" at \'%s\'", nextToken.c_str()).c_str(), context->path.GetText(), errLineNumber); // Append file context, if known. if (!context->fileContext.empty()) { s += " in file " + context->fileContext; } s += "\n"; // Return the line number in the error info. TfDiagnosticInfo info(errLineNumber); TF_ERROR(info, TF_DIAGNOSTIC_RUNTIME_ERROR_TYPE, s); context->seenError = true; } static void _ReportParseError(Sdf_TextParserContext *context, const std::string &text) { if (!context->values.IsRecordingString()) { textFileFormatYyerror(context, text.c_str()); } } // Helper class for generating/managing the buffer used by flex. // // This simply reads the given file entirely into memory, padded as flex // requires, and passes it along. Normally, flex reads data from a given file in // blocks of 8KB, which leads to O(n^2) behavior when trying to match strings // that are over this size. Giving flex a pre-filled buffer avoids this // behavior. struct Sdf_MemoryFlexBuffer : public boost::noncopyable { public: Sdf_MemoryFlexBuffer(const std::shared_ptr& asset, const std::string& name, yyscan_t scanner); ~Sdf_MemoryFlexBuffer(); yy_buffer_state *GetBuffer() { return _flexBuffer; } private: yy_buffer_state *_flexBuffer; std::unique_ptr _fileBuffer; yyscan_t _scanner; }; Sdf_MemoryFlexBuffer::Sdf_MemoryFlexBuffer( const std::shared_ptr& asset, const std::string& name, yyscan_t scanner) : _flexBuffer(nullptr) , _scanner(scanner) { // flex requires 2 bytes of null padding at the end of any buffers it is // given. We'll allocate a buffer with 2 padding bytes, then read the // entire file in. static const size_t paddingBytesRequired = 2; size_t size = asset->GetSize(); std::unique_ptr buffer(new char[size + paddingBytesRequired]); if (asset->Read(buffer.get(), size, 0) != size) { TF_RUNTIME_ERROR("Failed to read asset contents @%s@: " "an error occurred while reading", name.c_str()); return; } // Set null padding. memset(buffer.get() + size, '\0', paddingBytesRequired); _fileBuffer = std::move(buffer); _flexBuffer = textFileFormatYy_scan_buffer( _fileBuffer.get(), size + paddingBytesRequired, _scanner); } Sdf_MemoryFlexBuffer::~Sdf_MemoryFlexBuffer() { if (_flexBuffer) textFileFormatYy_delete_buffer(_flexBuffer, _scanner); } #ifdef SDF_PARSER_DEBUG_MODE extern int yydebug; #else static int yydebug; #endif // SDF_PARSER_DEBUG_MODE namespace { struct _DebugContext { explicit _DebugContext(bool state=true) : _old(yydebug) { yydebug = state; } ~_DebugContext() { yydebug = _old; } private: bool _old; }; } /// Parse a .menva file into an SdfData bool Sdf_ParseMenva( const std::string& fileContext, const std::shared_ptr& asset, const std::string& magicId, const std::string& versionString, bool metadataOnly, SdfDataRefPtr data) { TfAutoMallocTag2 tag("Menva", "Menva_Parse"); TRACE_FUNCTION(); // Turn on debugging, if enabled. _DebugContext debugCtx; // Configure for input file. Sdf_TextParserContext context; context.data = data; context.fileContext = fileContext; context.magicIdentifierToken = magicId; context.versionString = versionString; context.metadataOnly = metadataOnly; context.values.errorReporter = std::bind(_ReportParseError, &context, std::placeholders::_1); // Initialize the scanner, allowing it to be reentrant. textFileFormatYylex_init(&context.scanner); textFileFormatYyset_extra(&context, context.scanner); int status = -1; { Sdf_MemoryFlexBuffer input(asset, fileContext, context.scanner); yy_buffer_state *buf = input.GetBuffer(); // Continue parsing if we have a valid input buffer. If there // is no buffer, the appropriate error will have already been emitted. if (buf) { try { TRACE_SCOPE("textFileFormatYyParse"); status = textFileFormatYyparse(&context); } catch (boost::bad_get) { TF_CODING_ERROR("Bad boost:get() in menva parser."); Err(&context, "Internal menva parser error."); } } } // Note that the destructor for 'input' calls // textFileFormatYy_delete_buffer(), which requires a valid scanner // object. So we need 'input' to go out of scope before we can destroy the // scanner. textFileFormatYylex_destroy(context.scanner); return status == 0; } /// Parse a .menva string into an SdfData bool Sdf_ParseMenvaFromString(const std::string & menvaString, const std::string & magicId, const std::string & versionString, SdfDataRefPtr data) { TfAutoMallocTag2 tag("Menva", "Menva_Parse"); TRACE_FUNCTION(); // Configure for input string. Sdf_TextParserContext context; context.data = data; context.magicIdentifierToken = magicId; context.versionString = versionString; context.values.errorReporter = std::bind(_ReportParseError, &context, std::placeholders::_1); // Initialize the scanner, allowing it to be reentrant. textFileFormatYylex_init(&context.scanner); textFileFormatYyset_extra(&context, context.scanner); // Run parser. yy_buffer_state *buf = textFileFormatYy_scan_string(menvaString.c_str(), context.scanner); int status = -1; try { TRACE_SCOPE("textFileFormatYyParse"); status = textFileFormatYyparse(&context); } catch (boost::bad_get) { TF_CODING_ERROR("Bad boost:get() in menva parser."); Err(&context, "Internal menva parser error."); } // Clean up. textFileFormatYy_delete_buffer(buf, context.scanner); textFileFormatYylex_destroy(context.scanner); return status == 0; }