text: #include <rff/fgd/fgd.hpp>
  
  #include <stdexcept>
  #include <ranges>
  
  #include <rff/fgd/parse_tree.hpp>
  
  namespace rff
  {
  	namespace fgd
  	{
  		fgd::fgd(const std::string& data)
  		{
  			parse(data);
  		}
  
  		fgd& fgd::operator=(const std::string& data)
  		{
  			_entities.clear();
  			parse(data);
  			return *this;
  		}
  
  		void fgd::parse(const std::string& data)
  		{
  			parse_tree pt;
  			pt.parse(data);
  
  			for(
  				const auto rng = pt.classes();
  				auto& c : std::ranges::subrange(rng.first, rng.second))
  			{
  				// Check if we have enough properties to get classname
  				if(c._properties.empty())
  					throw std::logic_error("FGD: Failed to parse an unknown entity");
  				
  				auto& classname = c._properties[0].as_string();
  
  				// Check if we have enough properties for entity
  				if (c._properties.size() < 2)
  					throw std::logic_error(std::string("FGD: Failed to parse entity ") + classname);
  				
  				// Entity's class name
  				auto& ent = _entities[classname];
  
  				// Class Type
  				ent._class_type = to_class_type(c._class_type.as_string());
  
  				// Class Properties
  				for(auto& cp : c._attributes)
  				{
  					// TODO: deal with halfgridsnap, it's not a function (probably)
  					ent._class_properties.emplace_back(
  						to_prop(cp.as_function().function_name())
  					)
  					->parse(cp.as_function().parameters());
  				}
  
  				// Class Name
  				ent._class_name = classname;
  
  				if (c._properties.size() == 3) // description is optional
  					ent._description = c._properties[1].as_string();
  
  				if(!c._properties.back().is_collection())
  					throw std::logic_error(std::string("FGD: Entity ") + classname + " doesen't have properties.");
  				
  				// Member Variables
  				for(const auto& prop : c._properties.back().as_collection()._elements)
  				{
  					if(!prop._attributes[0].is_function())
  						throw std::logic_error(std::string("FGD: Failed to parse attributes of ") + classname);
  
  					// Input
  					if(prop._attributes[0].as_function().parameters()[0] == "input")
  					{
  						auto& i = ent._inputs.emplace_back();
  
  						i._name						= prop._attributes[0].as_function().function_name();
  						i._type						= to_type(prop._attributes[0].as_function().parameters()[1]);
  
  						// Description is optional
  						if (prop._attributes.size() > 1)
  							i._description			= prop._attributes[1].as_string();
  					}
  					
  					// Output
  					else if (prop._attributes[0].as_function().parameters()[0] == "output")
  					{
  						auto& o = ent._outputs.emplace_back();
  
  						o._name							= prop._attributes[0].as_function().function_name();
  						o._type							= to_type(prop._attributes[0].as_function().parameters()[1]);
  
  						if(prop._attributes.size() > 1)
  							o._description				= prop._attributes[1].as_string();
  
  						if (prop._attributes.size() > 2)
  							o._default_value			= prop._attributes[2].as_string();
  
  						if (prop._attributes.size() > 3)
  							o._parameter_description	= prop._attributes[3].as_string();
  					}
  
  					// Flags
  					else if (prop._attributes[0].as_function().function_name() == "spawnflags")
  					{
  						// TODO:
  					}
  
  					// Choices
  					else if (prop._attributes[0].as_function().parameters()[0] == "choices")
  					{
  						// TODO:
  					}
  
  					// Property
  					else
  					{
  						auto& p = ent._properties.emplace_back();
  
  						p._name					= prop._attributes[0].as_function().function_name();
  						p._type					= to_type(prop._attributes[0].as_function().parameters()[0]);
  
  						if (prop._attributes.size() > 1)
  							p._alias			= prop._attributes[1].as_string();
  						else
  							p._alias			= p._name;
  						
  						if (prop._attributes.size() > 2)
  							p._default_value	= prop._attributes[2].as_string();
  
  						if (prop._attributes.size() > 3)
  							p._description		= prop._attributes[3].as_string();
  					}
  				}
  			}
  		}
  
  		const entity* fgd::entity(const std::string& string) const
  		{
  			auto r =_entities.find(string);
  			if (r == std::end(_entities))
  				return nullptr;
  
  			return &r->second;
  		}
  	}
  }