Commits

Jason McKesson committed 064b25b

glscene: Start of scene node parsing.

  • Participants
  • Parent commits 6e350b8

Comments (0)

Files changed (8)

docs/glscene_notepadpp.xml

 <NotepadPlus>
-    <UserLang name="GL Scene Graph" ext="glscene" udlVersion="2.0">
+    <UserLang name="GL Scene Graph" ext="glscene" udlVersion="2.1">
         <Settings>
-            <Global caseIgnored="no" allowFoldOfComments="no" forceLineCommentsAtBOL="no" foldCompact="no" />
+            <Global caseIgnored="no" allowFoldOfComments="no" foldCompact="no" forcePureLC="0" decimalSeparator="0" />
             <Prefix Keywords1="no" Keywords2="no" Keywords3="no" Keywords4="no" Keywords5="no" Keywords6="no" Keywords7="no" Keywords8="no" />
         </Settings>
         <KeywordLists>
-            <Keywords name="Comments" id="0">00// 01 02 03 04</Keywords>
-            <Keywords name="Numbers, additional" id="1"></Keywords>
-            <Keywords name="Numbers, prefixes" id="2"></Keywords>
-            <Keywords name="Numbers, extras with prefixes" id="3"></Keywords>
-            <Keywords name="Numbers, suffixes" id="4"></Keywords>
-            <Keywords name="Operators1" id="5"></Keywords>
-            <Keywords name="Operators2" id="6"></Keywords>
-            <Keywords name="Folders in code1, open" id="7">resources sampler_res camera_res program_res</Keywords>
-            <Keywords name="Folders in code1, middle" id="8"></Keywords>
-            <Keywords name="Folders in code1, close" id="9">end</Keywords>
-            <Keywords name="Folders in code2, open" id="10"></Keywords>
-            <Keywords name="Folders in code2, middle" id="11"></Keywords>
-            <Keywords name="Folders in code2, close" id="12"></Keywords>
-            <Keywords name="Folders in comment, open" id="13"></Keywords>
-            <Keywords name="Folders in comment, middle" id="14"></Keywords>
-            <Keywords name="Folders in comment, close" id="15"></Keywords>
-            <Keywords name="Keywords1" id="16">compare mag min aniso wrap_s wrap_t wrap_r target orient spin radius radius_delta pos_delta rotation_scale placeholder vert tess_ctrl tess_eval geom frag mtc nmtc nctm sampler image uniform_buffer storage_buffer</Keywords>
-            <Keywords name="Keywords2" id="17">uniform_res uniform_buffer_res storage_buffer_res texture_res mesh_res</Keywords>
-            <Keywords name="Keywords3" id="18"></Keywords>
-            <Keywords name="Keywords4" id="19"></Keywords>
-            <Keywords name="Keywords5" id="20"></Keywords>
-            <Keywords name="Keywords6" id="21"></Keywords>
-            <Keywords name="Keywords7" id="22"></Keywords>
-            <Keywords name="Keywords8" id="23"></Keywords>
-            <Keywords name="Delimiters" id="24">00&lt; 01 02&gt; 03&quot; 04 05&quot; 06&apos; 07 08&apos; 09{ 10 11} 12[ 13 14] 15 16 17 18 19 20 21 22 23</Keywords>
+            <Keywords name="Comments">00// 01 02 03 04</Keywords>
+            <Keywords name="Numbers, prefix1"></Keywords>
+            <Keywords name="Numbers, prefix2"></Keywords>
+            <Keywords name="Numbers, extras1"></Keywords>
+            <Keywords name="Numbers, extras2"></Keywords>
+            <Keywords name="Numbers, suffix1"></Keywords>
+            <Keywords name="Numbers, suffix2"></Keywords>
+            <Keywords name="Numbers, range"></Keywords>
+            <Keywords name="Operators1"></Keywords>
+            <Keywords name="Operators2"></Keywords>
+            <Keywords name="Folders in code1, open">resources sampler_res camera_res program_res scene</Keywords>
+            <Keywords name="Folders in code1, middle"></Keywords>
+            <Keywords name="Folders in code1, close">end</Keywords>
+            <Keywords name="Folders in code2, open"></Keywords>
+            <Keywords name="Folders in code2, middle"></Keywords>
+            <Keywords name="Folders in code2, close"></Keywords>
+            <Keywords name="Folders in comment, open"></Keywords>
+            <Keywords name="Folders in comment, middle"></Keywords>
+            <Keywords name="Folders in comment, close"></Keywords>
+            <Keywords name="Keywords1">compare mag min aniso wrap_s wrap_t wrap_r target orient spin radius radius_delta pos_delta rotation_scale placeholder vert tess_ctrl tess_eval geom frag mtc nmtc nctm sampler image uniform_buffer storage_buffer layer_defs variant_check</Keywords>
+            <Keywords name="Keywords2">uniform_res uniform_buffer_res storage_buffer_res texture_res mesh_res</Keywords>
+            <Keywords name="Keywords3"></Keywords>
+            <Keywords name="Keywords4"></Keywords>
+            <Keywords name="Keywords5"></Keywords>
+            <Keywords name="Keywords6"></Keywords>
+            <Keywords name="Keywords7"></Keywords>
+            <Keywords name="Keywords8"></Keywords>
+            <Keywords name="Delimiters">00&lt; 01 02&gt; 03&quot; 04 05&quot; 06&apos; 07 08&apos; 09{ 10 11} 12[ 13 14] 15 16 17 18 19 20 21 22 23</Keywords>
         </KeywordLists>
         <Styles>
-            <WordsStyle name="DEFAULT" styleID="0" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
-            <WordsStyle name="COMMENTS" styleID="1" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
-            <WordsStyle name="LINE COMMENTS" styleID="2" fgColor="FF0000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
-            <WordsStyle name="NUMBERS" styleID="3" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
-            <WordsStyle name="KEYWORDS1" styleID="4" fgColor="0000FF" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
-            <WordsStyle name="KEYWORDS2" styleID="5" fgColor="0000FF" bgColor="FFFFFF" fontName="" fontStyle="1" nesting="0" />
-            <WordsStyle name="KEYWORDS3" styleID="6" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
-            <WordsStyle name="KEYWORDS4" styleID="7" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
-            <WordsStyle name="KEYWORDS5" styleID="8" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
-            <WordsStyle name="KEYWORDS6" styleID="9" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
-            <WordsStyle name="KEYWORDS7" styleID="10" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
-            <WordsStyle name="KEYWORDS8" styleID="11" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
-            <WordsStyle name="OPERATORS" styleID="12" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
-            <WordsStyle name="FOLDER IN CODE1" styleID="13" fgColor="0000FF" bgColor="FFFFFF" fontName="" fontStyle="1" nesting="0" />
-            <WordsStyle name="FOLDER IN CODE2" styleID="14" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
-            <WordsStyle name="FOLDER IN COMMENT" styleID="15" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
-            <WordsStyle name="DELIMITERS1" styleID="16" fgColor="7B7BC0" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
-            <WordsStyle name="DELIMITERS2" styleID="17" fgColor="008000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
-            <WordsStyle name="DELIMITERS3" styleID="18" fgColor="00B3B3" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
-            <WordsStyle name="DELIMITERS4" styleID="19" fgColor="8B0000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
-            <WordsStyle name="DELIMITERS5" styleID="20" fgColor="A000A0" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
-            <WordsStyle name="DELIMITERS6" styleID="21" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
-            <WordsStyle name="DELIMITERS7" styleID="22" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
-            <WordsStyle name="DELIMITERS8" styleID="23" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
+            <WordsStyle name="DEFAULT" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
+            <WordsStyle name="COMMENTS" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
+            <WordsStyle name="LINE COMMENTS" fgColor="FF0000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
+            <WordsStyle name="NUMBERS" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
+            <WordsStyle name="KEYWORDS1" fgColor="0000FF" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
+            <WordsStyle name="KEYWORDS2" fgColor="0000FF" bgColor="FFFFFF" fontName="" fontStyle="1" nesting="0" />
+            <WordsStyle name="KEYWORDS3" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
+            <WordsStyle name="KEYWORDS4" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
+            <WordsStyle name="KEYWORDS5" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
+            <WordsStyle name="KEYWORDS6" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
+            <WordsStyle name="KEYWORDS7" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
+            <WordsStyle name="KEYWORDS8" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
+            <WordsStyle name="OPERATORS" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
+            <WordsStyle name="FOLDER IN CODE1" fgColor="0000FF" bgColor="FFFFFF" fontName="" fontStyle="1" nesting="0" />
+            <WordsStyle name="FOLDER IN CODE2" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
+            <WordsStyle name="FOLDER IN COMMENT" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
+            <WordsStyle name="DELIMITERS1" fgColor="7B7BC0" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
+            <WordsStyle name="DELIMITERS2" fgColor="008000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
+            <WordsStyle name="DELIMITERS3" fgColor="00B3B3" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
+            <WordsStyle name="DELIMITERS4" fgColor="8B0000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
+            <WordsStyle name="DELIMITERS5" fgColor="A000A0" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
+            <WordsStyle name="DELIMITERS6" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
+            <WordsStyle name="DELIMITERS7" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
+            <WordsStyle name="DELIMITERS8" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
         </Styles>
     </UserLang>
 </NotepadPlus>

docs/sceneTest.glscene

 
 scene
 	//Defines all layers
-	layers 'main' 'shadow1' 'shadow2'
+	layer_defs 'main' 'shadow1' 'shadow2'
+	
+	//List of all legal variant names.
+	variant_check <test>
 
 	node <globals>
 		layer inherit 'main'	//Layer for the current node. All children will inherit these.

glscene/Test/test.glscene

 		aniso 1
 	end
 	
-	uniform_res <ident> [vec3] (5.0)
+	uniform_res <ident> [vec3] (-5.0)
 	
 	camera_res <ident> [left_btn] [left_kbd]
-		target (0 5 0)
+		target (0 -5 0)
 		orient (0 0 0 1)
 		spin 0
 		radius 10 1 20
 	texture_res <ident> "foo.file[ground_plane]"
 end
 
+scene
+	layer_defs 'foo' 'bar'
+//	variant_check <bla> <blabal>
+end

glscene/glscene_format.doxy

 - The `placeholder` keyword. This causes the system to create an incomplete texture resource. So the user is expected to define the texture later with glscene::ResourceRef::DefineTexture().
 - A "" filename string. This is a filename that will be loaded by the loader.
 
-
-
 # Scene Graph # {#page_glscene_file_format_syntax_scene}
 
 ~~~~
 end
 ~~~~
 
-The last command in a file must be a `scene` command. A file can only contain one `scene` command; it may not have more or fewer. And no `resources` commands may follow it.
+The last command in a file must be a `scene` command. A file can only contain one `scene` command; it may not have more or fewer. And no `resources` commands (or any other commands) may follow it. The `scene` command is a compound command.
 
-The `scene` command is a compound command, so it is terminated by `end`.
+The `scene` command is more rigidly structured in its content than the top-level `resources` command. The following commands must be provided, in order. Some of them are optional:
+
+- `layer_defs`: Required. The parameters to this command are a series of graph names. These represent the names of the layers, as provided to glscene::SceneGraph::SceneGraph(), and the order you place them in the list is the order that they will be provided in.
+- `variant_check`: Optional. The parameters to this command are a series of identifier strings. If you provide this command, the use of any variant names not listed here will result in a parse error.
+- One or more `node` commands, as described below.
+
+\par A Word on the Root Node
+The [GL Scene Graph](\ref module_glscene) has a root node. And glscene::SceneGraph freely allows you to fetch this node, add variants to it, change its transforms, and play with it as though it were any other node (mostly). However, the scene graph file format does not expose this functionality. You cannot set any of these things on the root node. This is quite deliberate.
+\par
+Becaue the root node is restricted in its functionality, such as re-parenting and deleting, the scene file format encourages you to create your own root. This makes it easier for you to move things around dynamically. It also makes it easier for you to change things later, since you can copy node definitions around more freely.
+
+## Scene Node ## {#page_glscene_file_format_syntax_scene_node}
+
 
 **/
+
+
+
+

glscene/source/Parse.cpp

 	// Token string names.
 	static const char *g_keywordStrings[][3] =
 	{
-#define MAC(name, pattern, debug_name, display_name) {pattern, debug_name, display_name},
+		//Use the keyword pattern as the display name.
+#define MAC(name, pattern, debug_name, display_name) {pattern, debug_name, pattern},
 #include "keywords.incl"
 #undef MAC
 	};
 		size_t prefix = GetPrefixFromId(id);
 		switch(prefix)
 		{
-		case KEYWORD_ID_PREFIX:				return "Keyword";
+		case KEYWORD_ID_PREFIX:				return "Command";
 		case WHITESPACE_ID_PREFIX:			return "Whitespace";
 		case STRING_ID_PREFIX:				return "String";
 		case NUMBER_ID_PREFIX:				return "Number";
 
 }}
 
+namespace
+{
+	struct throw_current_position_t{};
+	throw_current_position_t curr_throw;
+}
 
 namespace glscene
 {
 		ParsedTextureMap textures;
 	};
 
+	struct ParsedSceneGraphDef
+	{
+		FilePosition pos;
+		std::vector<std::string> layers;
+		std::vector<IdString> variantChecks;
+	};
+
 	template<typename Range>
 	class SceneGraphParser
 	{
 
 		void ParseSceneGraph()
 		{
-			ParseResources();
+			while(HasToken(TOK_RESOURCES).get_value_or(false))
+				ParseResources();
+			ExpectToken(TOK_SCENE);
+			ParseScene();
+			if(HasAToken())
+				ThrowParseError("Unexpected extra data found.", curr_throw);
 		}
 
 		void ParseResources()
 		{
 			PosStackPusher push(*this);
 
-			ExpectToken(TOK_RESOURCES);
-			EatOneToken();
+			ExpectAndEatToken(TOK_RESOURCES);
 
-			while(!m_rng.empty() && m_rng.front().id() != TOK_END)
+			while(!IsCurrToken(TOK_END))
 			{
 				ExpectCategory(m_rng.front(), KEYWORD_ID_PREFIX);
 				typename Range::value_type tok = m_rng.front();
 				}
 			}
 
-			ExpectEndToken();
-			EatOneToken();
+			ExpectAndEatEndToken();
 		}
 
 	private:
 		void EatOneToken() {m_rng.advance_begin(1);}
 
+		void ParseScene()
+		{
+			PosStackPusher push(*this);
+
+			ExpectAndEatToken(TOK_SCENE);
+			m_scene.pos = m_posStack.top();
+
+			{
+				PosStackPusher push(*this);
+				ExpectAndEatToken(TOK_LAYER_DEFS);
+				while(IsCurrToken(TOK_GRAPH_NAME))
+				{
+					m_scene.layers.push_back(ParseGraphName());
+				}
+				if(m_scene.layers.empty())
+					ThrowParseError("You must provide at least one layer name in a `layer_defs`.");
+				if(!IsCurrTokenCategory(KEYWORD_ID_PREFIX))
+					ThrowParseError("`layer_defs` members must be '' strings.", curr_throw);
+			}
+
+			if(IsCurrToken(TOK_VARIANT_CHECK))
+			{
+				EatOneToken();
+				while(IsCurrToken(TOK_IDENTIFIER))
+				{
+					EatOneToken();
+				}
+				if(!IsCurrTokenCategory(KEYWORD_ID_PREFIX))
+					ThrowParseError("`variant_check` members must be identifier strings.", curr_throw);
+			}
+
+			ExpectAndEatEndToken();
+		}
+
 		template<typename MapType>
 		IdString ParseIdentifier(const MapType &search, bool mustFind, size_t currentCmd)
 		{
 		{
 			std::string msg = "The identifier name '" + idToken + "' refers to a " +
 				GetTokenErrorName(currentCmd) + " that has not been defined.";
-			throw BaseParseError(msg, idToken.size());
+			ThrowParseError(msg, curr_throw);
 		}
 
 		void MultipleIdentifierOfSameType(const std::string &idToken, size_t currentCmd,
 				str << std::setw(earlyDefPos.columnNumber - 1) << " ";
 			str << "^- here" << std::endl;
 
-			throw BaseParseError(str.str(), idToken.size());			
+			ThrowParseError(str.str(), curr_throw);			
 		}
 
+		//Expects the top of the stack to be the position of the containing command.
 		void ThrowIfNotFound(const TokenChecker &hasBeenFound,
-			array_ref<size_t> expectedIds, const size_t mainId)
+			array_ref<size_t> expectedIds, const size_t mainId) const
 		{
 			BOOST_FOREACH(size_t id, expectedIds)
 			{
 				if(hasBeenFound.find(id) == hasBeenFound.end())
 				{
-					const FilePosition &pos = m_posStack.top();
-
 					std::stringstream str;
 					str << "The command '" << GetTokenErrorName(mainId) << "' must have a ";
 					str << GetTokenErrorName(id) << " command within it." << std::endl;
-					str << "\tIt was defined in file '" << pos.fileName << "' line ";
-					str << pos.lineNumber << " column " << pos.columnNumber << std::endl;
-					str << "\t" << pos.theLine << std::endl;
-					str << "\t";
-					if(pos.columnNumber > 1)
-						str << std::setw(pos.columnNumber - 1) << " ";
-					str << "^- here" << std::endl;
-
-					throw BaseParseError(str.str());
+					ThrowParseError(str.str());
 				}
 			}
 		}
 						if(samplerData.sampler.maxAniso < 1.0f)
 						{
 							std::string msg = "The maximum anisotropic filtering value must be 1.0 or greater.";
-							throw BaseParseError(msg, GetTokenString().size());
+							ThrowParseError(msg, curr_throw);
 						}
 						EatOneToken();
 					}
 				if(!HasTokenOneOfNoEmpty(g_validCameraTokens))
 					throw UnexpectedDataError(m_rng.front(), "a valid camera command.", exp_message);
 
+				PosStackPusher push(*this);
 				const typename Range::value_type tok = m_rng.front();
 				CheckMultipleCommand(hasBeenFound, tok, TOK_CAMERA_RES);
 				EatOneToken();
 
-				FilePosition pos = GetPositionForCurrToken();
+//				FilePosition pos = GetPositionForCurrToken();
 				switch(tok.id())
 				{
 				case TOK_TARGET:
 				case TOK_SPIN:
 					cameraData.view.degSpinRotation = ParseSingleFloat(tok, TOK_CAMERA_RES);
 					if(cameraData.view.degSpinRotation < 0.0f)
-						throw BaseParseError("Spin angle must be greater than 0.", pos);
+						ThrowParseError("Spin angle must be greater than 0.");
 					break;
 				case TOK_RADIUS:
 					{
 						float maxRadius = ParseSingleFloat(tok, TOK_CAMERA_RES);
 
 						if(minRadius < 0.0f)
-							throw BaseParseError("The minimum radius must be larger than 0.", pos);
+							ThrowParseError("The minimum radius must be larger than 0.");
 
 						if(maxRadius < 0.0f)
-							throw BaseParseError("The maximum radius must be larger than 0.", pos);
+							ThrowParseError("The maximum radius must be larger than 0.");
 
 						if(minRadius >= maxRadius)
-							throw BaseParseError("The minimum radius must be less than the maximum radius.", pos);
+							ThrowParseError("The minimum radius must be less than the maximum radius.");
 
 						if(initRadius < minRadius)
-							throw BaseParseError("The initial radius must be larger than the minimum.", pos);
+							ThrowParseError("The initial radius must be larger than the minimum.");
 
 						if(initRadius > maxRadius)
-							throw BaseParseError("The initial radius must be smaller than the maximum.", pos);
+							ThrowParseError("The initial radius must be smaller than the maximum.");
 
 						cameraData.view.radius = initRadius;
 						cameraData.scale.minRadius = minRadius;
 						float largeRadius = ParseSingleFloat(tok, TOK_CAMERA_RES);
 
 						if(smallRadius < 0.0f)
-							throw BaseParseError("The small radius delta must be larger than 0.", pos);
+							ThrowParseError("The small radius delta must be larger than 0.");
 
 						if(largeRadius < 0.0f)
-							throw BaseParseError("The large radius delta must be larger than 0.", pos);
+							ThrowParseError("The large radius delta must be larger than 0.");
 
 						if(smallRadius >= largeRadius)
-							throw BaseParseError("The minimum radius delta must be less than the maximum.", pos);
+							ThrowParseError("The minimum radius delta must be less than the maximum.");
 
 						cameraData.scale.smallRadiusDelta = smallRadius;
 						cameraData.scale.largeRadiusDelta = largeRadius;
 						float largeDelta = ParseSingleFloat(tok, TOK_CAMERA_RES);
 
 						if(smallDelta < 0.0f)
-							throw BaseParseError("The small position delta must be larger than 0.", pos);
+							ThrowParseError("The small position delta must be larger than 0.");
 
 						if(largeDelta < 0.0f)
-							throw BaseParseError("The large position delta must be larger than 0.", pos);
+							ThrowParseError("The large position delta must be larger than 0.");
 
 						if(smallDelta >= largeDelta)
-							throw BaseParseError("The minimum position delta must be less than the maximum.", pos);
+							ThrowParseError("The minimum position delta must be less than the maximum.");
 
 						cameraData.scale.smallPosOffset = smallDelta;
 						cameraData.scale.largePosOffset = largeDelta;
 						float rotScale = ParseSingleFloat(tok, TOK_CAMERA_RES);
 
 						if(rotScale < 0.0f)
-							throw BaseParseError("The rotation scale must be larger than 0.", pos);
+							ThrowParseError("The rotation scale must be larger than 0.");
 
 						cameraData.scale.rotationScale = rotScale;
 					}
 			boost::optional<bool> test = HasToken(TOK_PLACEHOLDER);
 			if(!test)
 			{
-				throw BaseParseError("Ran out of data instead of finding a buffer `placeholder` or enumeration.", m_posStack.top());
+				ThrowParseError("Ran out of data instead of finding a buffer `placeholder` or enumeration.");
 			}
 			else if(test.get())
 			{
 						{
 							meshDef.params.push_back(ParseSingleUInt());
 						}
-						catch(BaseParseError &e)
+						catch(BaseParseError &)
 						{
 							std::stringstream str;
 							str << "The mesh generator '" << g_meshCreateEnumeration.enumerators[generatorIx] <<
 								"' requires " << numParams << " parameters.";
-							throw BaseParseError(str.str(), m_posStack.top());
+							ThrowParseError(str.str());
 						}
 					}
 
 						std::stringstream str;
 						str << "The mesh generator '" << g_meshCreateEnumeration.enumerators[generatorIx] <<
 							"' only takes " << numParams << " parameters.";
-						throw BaseParseError(str.str(), GetPositionForCurrToken());
+						ThrowParseError(str.str(), curr_throw);
 					}
 				}
 				break;
 				std::string testEnum = GetEnumFromToken(m_rng.front());
 				if(testEnum != "separate")
 				{
-					throw BaseParseError("Program resources can only use the 'separate' enumerator.",
-						GetPositionForCurrToken());
+					ThrowParseError("Program resources can only use the 'separate' enumerator.", curr_throw);
 				}
 				EatOneToken();
 				programDef.isSeparate = true;
 
 			if(programDef.shaders.empty())
 			{
-				throw BaseParseError("You must provide at least one shader file in a program resource.", m_posStack.top());
+				ThrowParseError("You must provide at least one shader file in a program resource.");
 			}
 		}
 
 			return std::string(rawToken.begin() + 1, rawToken.end() - 1);
 		}
 
+		std::string ParseGraphName()
+		{
+			ExpectToken(TOK_GRAPH_NAME);
+			std::string rawToken = GetTokenString();
+			EatOneToken();
+
+			return std::string(rawToken.begin() + 1, rawToken.end() - 1);
+		}
+
 		glm::vec3 ParseVec3(const Token &owningTok, size_t owningId)
 		{
 			ExpectAndEatToken(TOK_OPEN_PAREN);
 				if(3 == count)
 				{
 					std::string msg = "The '" + GetTokenErrorName(owningTok.id()) + "' only takes 3 numbers.";
-					throw BaseParseError(msg, GetTokenString().size());
+					ThrowParseError(msg, curr_throw);
 				}
 
 				ret[count] = boost::lexical_cast<float>(GetTokenString());
 			if(3 != count && 1 != count)
 			{
 				std::string msg = "The '" + GetTokenErrorName(owningTok.id()) + "' requires 3 numbers.";
-				throw BaseParseError(msg, GetTokenString().size());
+				ThrowParseError(msg, curr_throw);
 			}
 
 			if(1 == count)
 				if(4 == count)
 				{
 					std::string msg = "The '" + GetTokenErrorName(owningTok.id()) + "' quaternion takes 4 numbers.";
-					throw BaseParseError(msg, GetTokenString().size());
+					ThrowParseError(msg, curr_throw);
 				}
 
 				inputQuat[count] = boost::lexical_cast<float>(GetTokenString());
 			if(4 != count)
 			{
 				std::string msg = "The '" + GetTokenErrorName(owningTok.id()) + "' quaternion requires 4 numbers.";
-				throw BaseParseError(msg, GetTokenString().size());
+				ThrowParseError(msg, curr_throw);
 			}
 
 			ExpectAndEatToken(TOK_CLOSE_PAREN);
 			return ret;
 		}
 
+		//Returns true if the current token has the identifier. Returns false if not. *Throws* if no current token.
+		bool IsCurrToken(size_t idExpected)
+		{
+			if(m_rng.empty())
+				throw UnexpectedDataError(idExpected);
+			else
+				return HasTokenNoEmpty(idExpected);
+		}
+
+		bool IsCurrTokenCategory(size_t prefix)
+		{
+			if(m_rng.empty())
+				throw UnexpectedDataError(prefix, exp_cat);
+			else
+				return HasCategoryNoEmpty(prefix);
+		}
+
 		void ExpectAndEatToken(size_t idExpected)
 		{
 			if(m_rng.empty())
 		void ExpectEndToken() const
 		{
 			if(m_rng.empty())
-				throw BaseParseError("This compound command needs an 'end' token.", m_posStack.top());
+				ThrowParseError("This compound command needs an 'end' token.");
 			else
 			{
 				if(m_rng.front().id() != TOK_END)
-					throw BaseParseError("This compound command needs an 'end' token.", m_posStack.top());
+					ThrowParseError("This compound command needs an 'end' token.");
 			}
 		}
 
 			return std::string(m_rng.front().value().begin(), m_rng.front().value().end());
 		}
 
+		void ThrowParseError(const std::string &msg) const
+		{
+			throw BaseParseError(msg, m_posStack.top());
+		}
+
+		void ThrowParseError(const std::string &msg, throw_current_position_t) const
+		{
+			throw BaseParseError(msg, GetPositionForCurrToken());
+		}
+
 		pos_iterator &m_currIt;
 		Range m_rng;
 		std::stack<FilePosition> m_posStack;
 		ParsedResources m_resources;
+		ParsedSceneGraphDef m_scene;
 
 		class PosStackPusher
 		{

glscene/source/ParserExceptions.h

 			switch(GetPrefixFromId(idExpected))
 			{
 			case KEYWORD_ID_PREFIX:
-				return std::string("Expected to find the keyword '") + GetTokenErrorName(idExpected) + "'. ";
+				return std::string("Expected to find the command name '") + GetTokenErrorName(idExpected) + "'. ";
 			case STRING_ID_PREFIX:
 				return std::string("Expected to find a ") + GetTokenErrorName(idExpected) + " style string. ";
 			case NUMBER_ID_PREFIX:

glscene/source/keywords.incl

 	MAC(STORAGE_BUFFER,	"storage_buffer",	"storage buffer",		"storage buffer")
 	MAC(MESH_RES,		"mesh_res",		"mesh resource",			"texture resource")
 	MAC(TEXTURE_RES,	"texture_res",	"texture resource",			"mesh resource")
+	MAC(SCENE,			"scene",		"scene graph",				"scene graph")
+	MAC(LAYER_DEFS,		"layer_defs",	"layer definition",			"layer definition")
+	MAC(VARIANT_CHECK,	"variant_check",	"variant checking",		"variant checking")

glscene/source/strings.incl

 	MAC(GLSL_IDENT,		"\\{[a-zA-Z_][a-zA-Z0-9_\\[\\]\\.]*\\}",		"Shader Identifier",	"{Shader.Identifier}")
 	MAC(ENUMERATOR,		"\\[[a-z0-9_\\-]+\\]",	"Enumerator",			"[Enumerator]")
 	MAC(FILENAME,		"\\\"[^\\\"\\n\\t]+\\\"",	"Filename",				"\"Filename String\"")
-	MAC(STORE_NAME,		"\\\'[^\\\'\\n\\t]+\\\'",	"Stored Name",			"'Stored Name String'")
+	MAC(GRAPH_NAME,		"\\\'[^\\\'\\n\\t]+\\\'",	"Graph Name",			"'Scene Graph Name'")