Skip to main content

History: All-purpose script parser

Source of version: 5

Copy to clipboard
            {maketoc}

!!Custom Script Parser?
In many games, object data is stored in external files for easy adjustment and management. For example, in a racing game, each car might have it's own configuration file that specifies it's top speed, turning radius, etc. Many programmers use XML or a binary format to store this information.

If you've ever written a .material script for Ogre, you probably know that Ogre's C-style script syntax is very intuitive and compact. Now you can store any kind of data you want in Ogre C-style scripts, and and quickly and easily load them with this parser.

Like Ogre's .material scripts, this will automatically parse all your scripts when a resource group is loaded. Since all scripts are pre-parsed, the system can access scripts not by filename, but by script name; in other words, you can include multiple scripts per file and still access them individually.

This script parser was designed to be very lightweight and fast; it's only a few KBs of code, and can parse around 10 MB per second on an average PC (if your average script is ~1 KB, you can load around 10,000 scripts in one second).

!!Using the Parser
Using this script parser is very easy. Simply initialize the system by creating a ConfigScriptLoader instance. You don't even have to keep a pointer to the instance because ConfigScriptLoader is a singleton class:

{CODE(wrap="1", colors="c++")} new ConfigScriptLoader();
{CODE}
Make sure you create a ConfigScriptLoader class before loading any resource groups, otherwise it will miss them and your scripts won't get loaded.

Now, after loading your resource groups, the ConfigScriptLoader will have automatically parsed your scripts for you! All you need to do now is request the desired script, and process it's contents. Getting a script is simple:

{CODE(wrap="1", colors="c++")} ConfigNode *rootNode;
 rootNode = ConfigScriptLoader::getSingleton().getConfigScript("entity", "Crate");
{CODE}
ConfigScriptLoader::getConfigScript() will retrieve the root node of the specified script, which you can use to access any of it's data.

You may notice that the getConfigScript() requires two names; one for the script type, and another for the actual name. For example, the above code would load this script:

{CODE(wrap="1", colors="c++")} entity Crate{
   position 100 50 200
   rotation 0 0 0
   scale 1 1 1
 }
{CODE}
You may be wondering what file this script is coming from. The fact is, it doesn't matter! As long as your scripts have a .object extension (and this can be changed if you modify line 22 of ConfigScript.cpp), their filename is completely irrelevant. Since you access scripts by script name rather than file name, it makes no difference where the script is located. This is a big advantage since this allows you to organize your scripts any way you want without worrying about your game not being able to find it.

Now that you have a pointer to the root ConfigNode, you can access any of the data it contains. For example, to access the Y position coordinate (50) given in the above script example, you first find the "position" node, then access it's 2nd value (as you can see it contains 3 values in all: 100, 50, and 200).

{CODE(wrap="1", colors="c++")} float positionY = rootNode->findChild("position")->getValueF(1);
{CODE}
1 is used to access the 2nd position value since std::vector is 0-based (like any other C array). getValueF() is a variation of getValue() that automatically converts the value to a float. Other variations are included for doubles, ints, etc.

Note: When you shut down your application, don't forget to delete the ConfigScriptLoader instance - Ogre won't do this for you, so failing to do so will result in a memory leak. Since it's a singleton class, you can always delete it using this code:

{CODE(wrap="1", colors="c++")} delete ConfigScriptLoader::getSingletonPtr();
{CODE}
!!Source Files


__ ConfigScript.h __
{CODE(wrap="1", colors="c++")}//This code is public domain - you can do whatever you want with it
//Original author: John Judnich
#ifndef _CONFIGSCRIPT_H__
#define _CONFIGSCRIPT_H__

#include <OgreScriptLoader.h>
#include <OgreStringConverter.h>
#include <hash_map>
#include <vector>

class ConfigNode;

class ConfigScriptLoader: public Ogre::ScriptLoader
{
public:
    ConfigScriptLoader();
    ~ConfigScriptLoader();

    inline static ConfigScriptLoader &getSingleton() { return *singletonPtr; }
    inline static ConfigScriptLoader *getSingletonPtr() { return singletonPtr; }

    Ogre::Real getLoadingOrder() const;
    const Ogre::StringVector &getScriptPatterns() const;

    ConfigNode *getConfigScript(const Ogre::String &type, const Ogre::String &name);

    void parseScript(Ogre::DataStreamPtr &stream, const Ogre::String &groupName);
    
private:
    static ConfigScriptLoader *singletonPtr;

    Ogre::Real mLoadOrder;
    Ogre::StringVector mScriptPatterns;

    stdext::hash_map<Ogre::String, ConfigNode*> scriptList;
    
    //Parsing
    char *parseBuff, *parseBuffEnd, *buffPtr;
    size_t parseBuffLen;

    enum Token
    {
        TOKEN_Text,
        TOKEN_NewLine,
        TOKEN_OpenBrace,
        TOKEN_CloseBrace,
        TOKEN_EOF,
    };

    Token tok, lastTok;
    Ogre::String tokVal, lastTokVal;
    char *lastTokPos;

    void _parseNodes(ConfigNode *parent);
    void _nextToken();
    void _prevToken();
};

class ConfigNode
{
public:
    ConfigNode(ConfigNode *parent, const Ogre::String &name = "untitled");
    ~ConfigNode();

    inline void setName(const Ogre::String &name)
    {
        this->name = name;
    }

    inline Ogre::String &getName()
    {
        return name;
    }

    inline void addValue(const Ogre::String &value)
    {
        values.push_back(value);
    }

    inline void clearValues()
    {
        values.clear();
    }

    inline std::vector<Ogre::String> &getValues()
    {
        return values;
    }

    inline const Ogre::String &getValue(unsigned int index = 0)
    {
        assert(index < values.size());
        return values[index];
    }

    inline float getValueF(unsigned int index = 0)
    {
        assert(index < values.size());
        return Ogre::StringConverter::parseReal(values[index]);
    }

    inline double getValueD(unsigned int index = 0)
    {
        assert(index < values.size());

        std::istringstream str(values[index]);
        double ret = 0;
        str >> ret;
        return ret;
    }

    inline int getValueI(unsigned int index = 0)
    {
        assert(index < values.size());
        return Ogre::StringConverter::parseInt(values[index]);
    }
    
    ConfigNode *addChild(const Ogre::String &name = "untitled", bool replaceExisting = false);
    ConfigNode *findChild(const Ogre::String &name, bool recursive = false);

    inline std::vector<ConfigNode*> &getChildren()
    {
        return children;
    }

    inline ConfigNode *getChild(unsigned int index = 0)
    {
        assert(index < children.size());
        return children[index];
    }
    
    void setParent(ConfigNode *newParent);

    inline ConfigNode *getParent()
    {
        return parent;
    }

private:
    Ogre::String name;
    std::vector<Ogre::String> values;
    std::vector<ConfigNode*> children;
    ConfigNode *parent;

    int lastChildFound;  //The last child node's index found with a call to findChild()

    std::vector<ConfigNode*>::iterator _iter;
    bool _removeSelf;
};

#endif{CODE}

__ ConfigScript.cpp __
{CODE(wrap="1" colors="c++")}#include "ConfigScript.h"
#include "Exception.h"

#include <OgreScriptLoader.h>
#include <OgreScriptLoader.h>
#include <OgreResourceGroupManager.h>
using namespace Ogre;

#include <vector>
#include <hash_map>
using namespace std;
using namespace stdext;

ConfigScriptLoader *ConfigScriptLoader::singletonPtr = NULL;

ConfigScriptLoader::ConfigScriptLoader()
{
    //Init singleton
    if (singletonPtr)
        EXCEPTION("Multiple ConfigScriptManager objects are not allowed", "ConfigScriptManager::ConfigScriptManager()");
    singletonPtr = this;

    //Register as a ScriptLoader
    mLoadOrder = 100.0f;
    mScriptPatterns.push_back("*.object");
    ResourceGroupManager::getSingleton()._registerScriptLoader(this);
}

ConfigScriptLoader::~ConfigScriptLoader()
{
    singletonPtr = NULL;

    //Delete all scripts
    stdext::hash_map<String, ConfigNode*>::iterator i;
    for (i = scriptList.begin(); i != scriptList.end(); i++){
        delete i->second;
    }
    scriptList.clear();

    //Unregister with resource group manager
    if (ResourceGroupManager::getSingletonPtr())
        ResourceGroupManager::getSingleton()._unregisterScriptLoader(this);
}

Real ConfigScriptLoader::getLoadingOrder() const
{
    return mLoadOrder;
}

const StringVector &ConfigScriptLoader::getScriptPatterns() const
{
    return mScriptPatterns;
}

ConfigNode *ConfigScriptLoader::getConfigScript(const String &type, const String &name)
{
    stdext::hash_map<String, ConfigNode*>::iterator i;

    String key = type + ' ' + name;
    i = scriptList.find(key);

    //If found..
    if (i != scriptList.end())
        return i->second;
    else
        return NULL;
}

void ConfigScriptLoader::parseScript(DataStreamPtr &stream, const String &groupName)
{
    //Copy the entire file into a buffer for fast access
    parseBuffLen = stream->size();
    parseBuff = new char[parseBuffLen];
    buffPtr = parseBuff;
    stream->read(parseBuff, parseBuffLen);
    parseBuffEnd = parseBuff + parseBuffLen;

    //Close the stream (it's no longer needed since everything is in parseBuff)
    stream->close();

    //Get first token
    _nextToken();
    if (tok == TOKEN_EOF)
        return;
    
    //Parse the script
    _parseNodes(0);

    if (tok == TOKEN_CloseBrace)
        EXCEPTION("Parse Error: Closing brace out of place", "ConfigScript::load()");
    
    //Delete the buffer
    delete[] parseBuff;
}

void ConfigScriptLoader::_nextToken()
{
    lastTok = tok;
    lastTokVal = tokVal;
    lastTokPos = buffPtr;

    //EOF token
    if (buffPtr >= parseBuffEnd){
        tok = TOKEN_EOF;
        return;
    }

    //(Get next character)
    int ch = *buffPtr++;
    while (ch == ' ' || ch == 9){    //Skip leading spaces / tabs
        ch = *buffPtr++;
    }

    //Newline token
    if (ch == '\r' || ch == '\n'){
        do {
            ch = *buffPtr++;
        } while ((ch == '\r' || ch == '\n') && buffPtr < parseBuffEnd);
        buffPtr--;

        tok = TOKEN_NewLine;
        return;
    }

    //Open brace token
    else if (ch == '{'){
        tok = TOKEN_OpenBrace;
        return;
    }

    //Close brace token
    else if (ch == '}'){
        tok = TOKEN_CloseBrace;
        return;
    }

    //Text token
    if (ch < 32 || ch > 122)    //Verify valid char
        EXCEPTION("Parse Error: Invalid character", "ConfigScript::load()");

    tokVal = "";
    tok = TOKEN_Text;
    do {
        //Skip comments
        if (ch == '/'){
            int ch2 = *buffPtr;
            
            //C++ style comment (//)
            if (ch2 == '/'){
                buffPtr++;
                do {
                    ch = *buffPtr++;
                } while (ch != '\r' && ch != '\n' && buffPtr < parseBuffEnd);
                
                tok = TOKEN_NewLine;
                return;
            }
        }

        //Add valid char to tokVal
        tokVal += ch;

        //Next char
        ch = *buffPtr++;
    } while (ch > 32 && ch <= 122 && buffPtr < parseBuffEnd);
    buffPtr--;

    return;
}

void ConfigScriptLoader::_prevToken()
{
    tok = lastTok;
    tokVal = lastTokVal;
    buffPtr = lastTokPos;
}

void ConfigScriptLoader::_parseNodes(ConfigNode *parent)
{
    typedef std::pair<String, ConfigNode*> ScriptItem;

    while (1) {
        switch (tok){
            //Node
            case TOKEN_Text:
                //Add the new node
                ConfigNode *newNode;
                if (parent)
                    newNode = parent->addChild(tokVal);
                else
                    newNode = new ConfigNode(0, tokVal);

                //Get values
                _nextToken();
                while (tok == TOKEN_Text){
                    newNode->addValue(tokVal);
                    _nextToken();
                }

                //Add root nodes to scriptList
                if (!parent){
                    String key;

                    if (newNode->getValues().empty())
                        key = newNode->getName() + ' ';
                    else
                        key = newNode->getName() + ' ' + newNode->getValues().front();

                    scriptList.insert(ScriptItem(key, newNode));
                }

                //Skip any blank spaces
                while (tok == TOKEN_NewLine)
                    _nextToken();
                
                //Add any sub-nodes
                if (tok == TOKEN_OpenBrace){
                    //Parse nodes
                    _nextToken();
                    _parseNodes(newNode);
                    
                    //Skip blank spaces
                    while (tok == TOKEN_NewLine)
                        _nextToken();

                    //Check for matching closing brace
                    if (tok != TOKEN_CloseBrace)
                        EXCEPTION("Parse Error: Expecting closing brace", "ConfigScript::load()");
                } else {
                    //If it's not a opening brace, back up so the system will parse it properly
                    _prevToken();
                }
                                
                break;

            //Out of place brace
            case TOKEN_OpenBrace:
                EXCEPTION("Parse Error: Opening brace out of plane", "ConfigScript::load()");
                break;

            //Return if end of nodes have been reached
            case TOKEN_CloseBrace:
                return;

            //Return if reached end of file
            case TOKEN_EOF:
                return;
        }
    
        //Next token
        _nextToken();
    };
}

ConfigNode::ConfigNode(ConfigNode *parent, const String &name)
{
    ConfigNode::name = name;
    ConfigNode::parent = parent;
    _removeSelf = true;    //For proper destruction
    lastChildFound = -1;

    //Add self to parent's child list (unless this is the root node being created)
    if (parent != NULL){
        parent->children.push_back(this);
        _iter = --(parent->children.end());
    }
}

ConfigNode::~ConfigNode()
{
    //Delete all children
    std::vector<ConfigNode*>::iterator i;
    for (i = children.begin(); i != children.end(); i++){
        ConfigNode *node = *i;
        node->_removeSelf = false;
        delete node;
    }
    children.clear();

    //Remove self from parent's child list
    if (_removeSelf && parent != NULL)
        parent->children.erase(_iter);
}

ConfigNode *ConfigNode::addChild(const String &name, bool replaceExisting)
{
    if (replaceExisting) {
        ConfigNode *node = findChild(name, false);
        if (node)
            return node;
    }
    return new ConfigNode(this, name);
}

ConfigNode *ConfigNode::findChild(const String &name, bool recursive)
{
    int indx, prevC, nextC;
    int childCount = (int)children.size();

    if (lastChildFound != -1){
        //If possible, try checking the nodes neighboring the last successful search
        //(often nodes searched for in sequence, so this will boost search speeds).
        prevC = lastChildFound-1; if (prevC < 0) prevC = 0; else if (prevC >= childCount) prevC = childCount-1;
        nextC = lastChildFound+1; if (nextC < 0) nextC = 0; else if (nextC >= childCount) nextC = childCount-1;
        for (indx = prevC; indx <= nextC; ++indx){
            ConfigNode *node = children[indx];
            if (node->name == name) {
                lastChildFound = indx;
                return node;
            }
        }

        //If not found that way, search for the node from start to finish, avoiding the
        //already searched area above.
        for (indx = nextC + 1; indx < childCount; ++indx){
            ConfigNode *node = children[indx];
            if (node->name == name) {
                lastChildFound = indx;
                return node;
            }
        }
        for (indx = 0; indx < prevC; ++indx){
            ConfigNode *node = children[indx];
            if (node->name == name) {
                lastChildFound = indx;
                return node;
            }
        }
    } else {
        //Search for the node from start to finish
        for (indx = 0; indx < childCount; ++indx){
            ConfigNode *node = children[indx];
            if (node->name == name) {
                lastChildFound = indx;
                return node;
            }
        }
    }

    //If not found, search child nodes (if recursive == true)
    if (recursive){
        for (indx = 0; indx < childCount; ++indx){
            children[indx]->findChild(name, recursive);
        }
    }

    //Not found anywhere
    return NULL;
}

void ConfigNode::setParent(ConfigNode *newParent)
{
    //Remove self from current parent
    parent->children.erase(_iter);

    //Set new parent
    parent = newParent;

    //Add self to new parent
    parent->children.push_back(this);
    _iter = --(parent->children.end());
}{CODE}
---
Alias: (alias(All-purpose_script_parser))
        

History

Information Version
Mon 11 of Jun, 2012 09:50 GMT-0000 scrawl add changelog of my version 11
Mon 11 of Jun, 2012 09:47 GMT-0000 scrawl fixed a bug in the non-ogre dependant parser 10
Wed 25 of Apr, 2012 20:04 GMT-0000 rkeene Ogre free version with some corrections. 9
Wed 25 of Apr, 2012 19:32 GMT-0000 rkeene 8
Wed 25 of Apr, 2012 19:05 GMT-0000 rkeene Warning about non-portable code. 7
Wed 25 of Apr, 2012 18:59 GMT-0000 rkeene 6
Sun 31 of Jul, 2011 22:49 GMT-0000 jacmoe 5
Wed 30 of Jun, 2010 01:10 GMT-0000 JustBoo 4
Sun 03 of Jan, 2010 00:30 GMT-0000 jacmoe 3
Fri 25 of Dec, 2009 21:44 GMT-0000 jacmoe 2
Sat 11 of Oct, 2008 17:56 GMT-0000 OgreWikiBot Robot: Cosmetic changes 1