Skip to content

Commit

Permalink
libdeng2|Info: Embedding Doomsday Script in Info files
Browse files Browse the repository at this point in the history
The Info parser now supports embedded blocks of Doomsday Script.
By defaults blocks with type 'block' are parsed as Doomsday Script.
de::Info doesn't actually do anything with the script source, just
stores it as one key element inside the block.

Also, improvements to the Info parser:

- elements know their parents
- element ownership is held in the "inOrder" contents
- block names are optional and can use quotes
- block attribute values can use quotes
  • Loading branch information
skyjake committed May 10, 2013
1 parent f8a9d56 commit 8975fef
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 35 deletions.
35 changes: 29 additions & 6 deletions doomsday/libdeng2/include/de/data/info.h
Expand Up @@ -21,22 +21,28 @@
#define LIBDENG2_INFO_H

#include "../String"
#include "../NativePath"
#include <QStringList>
#include <QHash>

namespace de {

/**
* Key/value tree. Read from the "Snowberry" Info file format.
* Key/value tree. The tree is parsed from the "Snowberry" Info file format.
*
* See the Doomsday Wiki for an example of the syntax:
* http://dengine.net/dew/index.php?title=Info
*
* This implementation has been ported to C++ based on cfparser.py from
* Snowberry.
*
* @todo Document Info syntax in wiki, with example.
* @todo Should use de::Lex internally.
*/
class Info
{
public:
class BlockElement;

/**
* Base class for all elements.
*/
Expand All @@ -53,9 +59,14 @@ class Info
* @param t Type of the element.
* @param n Case-independent name of the element.
*/
Element(Type t = None, String const &n = "") : _type(t) { setName(n); }
Element(Type t = None, String const &n = "") : _type(t), _parent(0) { setName(n); }
virtual ~Element() {}

void setParent(BlockElement *parent) { _parent = parent; }
BlockElement *parent() const {
return _parent;
}

Type type() const { return _type; }
bool isKey() const { return _type == Key; }
bool isList() const { return _type == List; }
Expand All @@ -69,6 +80,7 @@ class Info
private:
Type _type;
String _name;
BlockElement *_parent;
};

/**
Expand Down Expand Up @@ -148,8 +160,9 @@ class Info

void add(Element *elem) {
DENG2_ASSERT(elem != 0);
_contentsInOrder.append(elem);
_contents.insert(elem->name(), elem);
elem->setParent(this);
_contentsInOrder.append(elem); // owned
_contents.insert(elem->name(), elem); // not owned (name may be empty)
}

Element *find(String const &name) const {
Expand Down Expand Up @@ -201,6 +214,14 @@ class Info
*/
Info(String const &source);

/**
* Sets all the block types whose content is parsed using a script parser.
* The blocks will contain a single KeyElement named "script".
*
* @param blocksToParseAsScript List of block types.
*/
void setScriptBlocks(QStringList const &blocksToParseAsScript);

/**
* Parses the Info contents from a text string.
*
Expand All @@ -213,7 +234,9 @@ class Info
*
* @param nativePath Path of a native file containing the Info source.
*/
void parseNativeFile(String const &nativePath);
void parseNativeFile(NativePath const &nativePath);

void clear();

BlockElement const &root() const;

Expand Down
130 changes: 101 additions & 29 deletions doomsday/libdeng2/src/data/info.cpp
Expand Up @@ -18,6 +18,9 @@
*/

#include "de/Info"
#include "de/ScriptLex"
#include "de/Log"
#include "de/LogBuffer"
#include <QFile>

using namespace de;
Expand All @@ -31,8 +34,19 @@ DENG2_PIMPL_NOREF(Info)
DENG2_ERROR(OutOfElements);
DENG2_ERROR(EndOfFile);

QStringList scriptBlockTypes;
String content;
int currentLine;
int cursor; ///< Index of the next character from the source.
QChar currentChar;
int tokenStartOffset;
String currentToken;
BlockElement rootBlock;

Instance() : currentLine(0), cursor(0), tokenStartOffset(0), rootBlock("", "")
{}
{
scriptBlockTypes << "script";
}

/**
* Initialize the parser for reading a block of source content.
Expand All @@ -47,6 +61,8 @@ DENG2_PIMPL_NOREF(Info)
content = source + "\n";
currentLine = 1;

currentChar = '\0';
cursor = 0;
nextChar();
tokenStartOffset = 0;

Expand Down Expand Up @@ -76,7 +92,10 @@ DENG2_PIMPL_NOREF(Info)
// No more characters to read.
throw EndOfFile(QString("EOF on line %1").arg(currentLine));
}
if(currentChar == '\n') currentLine++;
if(currentChar == '\n')
{
currentLine++;
}
currentChar = content[cursor];
cursor++;
}
Expand Down Expand Up @@ -160,6 +179,8 @@ DENG2_PIMPL_NOREF(Info)
catch(EndOfFile const &)
{}

//LOG_DEBUG("token: '%s' line %i") << currentToken << currentLine;

return currentToken;
}

Expand Down Expand Up @@ -236,11 +257,13 @@ DENG2_PIMPL_NOREF(Info)
// Double single quotes form a double quote ('' => ").
nextChar();
if(peekChar() == '\'')
{
chars.append("\"");
}
else
{
chars.append("'");
chars.append(peekChar());
continue;
}
}
else
Expand Down Expand Up @@ -366,17 +389,25 @@ DENG2_PIMPL_NOREF(Info)
*/
BlockElement *parseBlockElement(String const &blockType)
{
QScopedPointer<BlockElement> block(new BlockElement(blockType, peekToken()));
DENG2_ASSERT(blockType != "}");
DENG2_ASSERT(blockType != ")");

String blockName;
if(peekToken() != "(" && peekToken() != "{")
{
blockName = parseValue();
}

QScopedPointer<BlockElement> block(new BlockElement(blockType, blockName));
int startLine = currentLine;

// How about some attributes?
// Syntax: {token value} '('|'{'
String endToken;

try
{
String endToken;
// How about some attributes?
// Syntax: {token value} '('|'{'

nextToken();
while(peekToken() != "(" && peekToken() != "{")
{
String keyName = peekToken();
Expand All @@ -389,19 +420,54 @@ DENG2_PIMPL_NOREF(Info)

endToken = (peekToken() == "("? ")" : "}");

// Move past the opening parentheses.
nextToken();

while(peekToken() != endToken)
// Parse the contents of the block.
if(scriptBlockTypes.contains(blockType))
{
Element *element = parseElement();
if(!element)
// Parse as Doomsday Script.
int startPos = cursor - 1;
String remainder = content.substr(startPos);
ScriptLex lex(remainder);
try
{
TokenBuffer tokens;
while(lex.getStatement(tokens)) {}

// We should have gotten a mismatched bracket that ends the script.
throw SyntaxError("Info::parseBlockElement",
QString("Block element was never closed, end of file encountered before '%1' was found (on line %2).")
.arg(endToken).arg(currentLine));
QString("Script block at line %1 is not closed").arg(currentLine));
}
catch(ScriptLex::MismatchedBracketError const &)
{
// Continue parsing normally from here.
int endPos = startPos + lex.pos();
do {
nextChar();
} while(cursor < endPos);
currentToken = QString(peekChar());
nextChar();

block->add(new KeyElement("script", content.substr(startPos, lex.pos() - 1)));

//LOG_DEBUG("Script element:\n\"%s\"") << block->find("script")->values().first();
}
}
else
{
// Move past the opening parentheses.
nextToken();

// Parse normally as Info.
while(peekToken() != endToken)
{
Element *element = parseElement();
if(!element)
{
throw SyntaxError("Info::parseBlockElement",
QString("Block element was never closed, end of file encountered before '%1' was found (on line %2).")
.arg(endToken).arg(currentLine));
}
block->add(element);
}
block->add(element);
}
}
catch(EndOfFile const &)
Expand All @@ -411,6 +477,9 @@ DENG2_PIMPL_NOREF(Info)
.arg(startLine));
}

LogBuffer::appBuffer().flush();
DENG2_ASSERT(peekToken() == endToken);

// Move past the closing parentheses.
nextToken();

Expand All @@ -427,14 +496,6 @@ DENG2_PIMPL_NOREF(Info)
rootBlock.add(e);
}
}

String content;
int currentLine;
int cursor; ///< Index of the next character from the source.
QChar currentChar;
int tokenStartOffset;
String currentToken;
BlockElement rootBlock;
};

Info::BlockElement::~BlockElement()
Expand All @@ -444,9 +505,10 @@ Info::BlockElement::~BlockElement()

void Info::BlockElement::clear()
{
for(Contents::iterator i = _contents.begin(); i != _contents.end(); ++i)
delete i.value();

for(ContentsInOrder::iterator i = _contentsInOrder.begin(); i != _contentsInOrder.end(); ++i)
{
delete *i;
}
_contents.clear();
_contentsInOrder.clear();
}
Expand Down Expand Up @@ -489,12 +551,17 @@ Info::Info(String const &source)
d.reset(inst.take());
}

void Info::setScriptBlocks(QStringList const &blocksToParseAsScript)
{
d->scriptBlockTypes = blocksToParseAsScript;
}

void Info::parse(String const &infoSource)
{
d->parse(infoSource);
}

void Info::parseNativeFile(String const &nativePath)
void Info::parseNativeFile(NativePath const &nativePath)
{
QFile file(nativePath);
if(file.open(QFile::ReadOnly | QFile::Text))
Expand All @@ -503,6 +570,11 @@ void Info::parseNativeFile(String const &nativePath)
}
}

void Info::clear()
{
parse("");
}

Info::BlockElement const &Info::root() const
{
return d->rootBlock;
Expand Down

0 comments on commit 8975fef

Please sign in to comment.