Skip to content

Commit

Permalink
Resources|UDMF: Parsing the UDMF TEXTMAP lump contents
Browse files Browse the repository at this point in the history
When a UDMF map is recognized, the importudmf plugin is used to
read its contents. UDMFLex is based on de::Lex for tokenizing the
UDMF source, and UDMFParser makes a callback for each encountered
global assignment and block.
  • Loading branch information
skyjake committed Nov 17, 2016
1 parent 6337333 commit 3c7abf9
Show file tree
Hide file tree
Showing 6 changed files with 474 additions and 30 deletions.
2 changes: 1 addition & 1 deletion doomsday/apps/plugins/importudmf/include/importudmf.h
Expand Up @@ -23,7 +23,7 @@
#include <doomsday.h> // Doomsday's Public API

//DENG_USING_API(Base);
//DENG_USING_API(F);
DENG_USING_API(F);
DENG_USING_API(Map);
DENG_USING_API(Material);
DENG_USING_API(MPE);
Expand Down
62 changes: 62 additions & 0 deletions doomsday/apps/plugins/importudmf/include/udmflex.h
@@ -0,0 +1,62 @@
/** @file udmflex.h UDMF lexical analyzer.
*
* @authors Copyright (c) 2016 Jaakko Keränen <jaakko.keranen@iki.fi>
*
* @par License
* GPL: http://www.gnu.org/licenses/gpl.html
*
* <small>This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. This program is distributed in the hope that it
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details. You should have received a copy of the GNU
* General Public License along with this program; if not, see:
* http://www.gnu.org/licenses</small>
*/

#ifndef IMPORTUDMF_UDMFLEX_H
#define IMPORTUDMF_UDMFLEX_H

#include <de/Lex>
#include <de/TokenBuffer>

class UDMFLex : public de::Lex
{
public:
// Keywords.
static de::String const NAMESPACE;
static de::String const LINEDEF;
static de::String const SIDEDEF;
static de::String const VERTEX;
static de::String const SECTOR;
static de::String const THING;
static de::String const T_TRUE;
static de::String const T_FALSE;

// Operators.
static de::String const ASSIGN;

// Literals.
static de::String const BRACKET_OPEN;
static de::String const BRACKET_CLOSE;
static de::String const SEMICOLON;

public:
UDMFLex(de::String const &input = "");

de::dsize getExpression(de::TokenBuffer &output);

/**
* Parse a string.
*
* @param output Output token buffer.
*/
void parseString(de::TokenBuffer &output);

/// Determines whether a token is a keyword.
static bool isKeyword(de::Token const &token);
};

#endif // IMPORTUDMF_UDMFLEX_H
65 changes: 65 additions & 0 deletions doomsday/apps/plugins/importudmf/include/udmfparser.h
@@ -0,0 +1,65 @@
/** @file udmfparser.h UDMF parser.
*
* @authors Copyright (c) 2016 Jaakko Keränen <jaakko.keranen@iki.fi>
*
* @par License
* GPL: http://www.gnu.org/licenses/gpl.html
*
* <small>This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. This program is distributed in the hope that it
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details. You should have received a copy of the GNU
* General Public License along with this program; if not, see:
* http://www.gnu.org/licenses</small>
*/

#ifndef IMPORTUDMF_UDMFPARSER_H
#define IMPORTUDMF_UDMFPARSER_H

#include "udmflex.h"
#include <de/TokenRange>
#include <QHash>
#include <functional>

/**
* UMDF parser.
*
* Reads input text and makes callbacks for each parsed block.
*/
class UDMFParser
{
public:
typedef QHash<de::String, QVariant> Block;
typedef std::function<void (de::String const &, QVariant const &)> AssignmentFunc;
typedef std::function<void (de::String const &, Block const &)> BlockFunc;

DENG2_ERROR(SyntaxError);

public:
UDMFParser();

void setGlobalAssignmentHandler(AssignmentFunc func);
void setBlockHandler(BlockFunc func);

Block const &globals() const;

void parse(de::String const &input);

protected:
de::dsize nextExpression();
void parseBlock(Block &block);
void parseAssignment(Block &block);

private:
AssignmentFunc _assignmentHandler;
BlockFunc _blockHandler;
UDMFLex _analyzer;
de::TokenBuffer _tokens;
de::TokenRange _range;
Block _globals;
};

#endif // IMPORTUDMF_UDMFPARSER_H
63 changes: 34 additions & 29 deletions doomsday/apps/plugins/importudmf/src/importudmf.cpp
Expand Up @@ -18,6 +18,7 @@
*/

#include "importudmf.h"
#include "udmfparser.h"

#include <doomsday/filesys/lumpindex.h>
#include <de/App>
Expand All @@ -32,36 +33,42 @@ using namespace de;
* Our job is to read in the map data structures then use the Doomsday map editing
* interface to recreate the map in native format.
*/
static int convertMapHook(int /*hookType*/, int /*parm*/, void *context)
static int importMapHook(int /*hookType*/, int /*parm*/, void *context)
{
Id1MapRecognizer const *recognizer = reinterpret_cast<Id1MapRecognizer *>(context);
if (!recognizer) return false;

if (recognizer->format() == Id1MapRecognizer::UniversalFormat)
if (Id1MapRecognizer const *recognizer = reinterpret_cast<Id1MapRecognizer *>(context))
{
LOG_AS("UDMFConverter");

// Attempt a conversion...
try
if (recognizer->format() == Id1MapRecognizer::UniversalFormat)
{
#if 0
std::unique_ptr<MapImporter> map(new MapImporter(recognizer));
LOG_AS("importudmf");
try
{
// Read the contents of the TEXTMAP lump.
auto *src = recognizer->lumps()[Id1MapRecognizer::UDMFTextmapData];
Block bytes(src->size());
src->read(bytes.data(), false);

// The archived map data was read successfully.
// Transfer to the engine via the runtime map editing interface.
/// @todo Build it using native components directly...
LOG_AS("importidtech1");
map->transfer();
#endif
return true; // success
}
catch (Error const &er)
{
LOG_MAP_ERROR("Erroring while loading UDMF: ") << er.asText();
String const source = String::fromUtf8(bytes);

// Parse the UDMF source and use the MPE API to create the map elements.
UDMFParser parser;
parser.setGlobalAssignmentHandler([] (String const &ident, QVariant const &value)
{
qDebug() << "Global:" << ident << value;
});
parser.setBlockHandler([] (String const &type, UDMFParser::Block const &block)
{
qDebug() << "Block" << type << block;
});
parser.parse(source);
return true;
}
catch (Error const &er)
{
LOG_MAP_ERROR("Erroring while loading UDMF: ") << er.asText();
}
}
}

return false; // failure :(
return false;
}

/**
Expand All @@ -70,7 +77,7 @@ static int convertMapHook(int /*hookType*/, int /*parm*/, void *context)
*/
extern "C" void DP_Initialize()
{
Plug_AddHook(HOOK_MAP_CONVERT, convertMapHook);
Plug_AddHook(HOOK_MAP_CONVERT, importMapHook);
}

/**
Expand All @@ -83,17 +90,15 @@ extern "C" char const *deng_LibraryType()
}

//DENG_DECLARE_API(Base);
//DENG_DECLARE_API(F);
DENG_DECLARE_API(F);
DENG_DECLARE_API(Map);
DENG_DECLARE_API(Material);
DENG_DECLARE_API(MPE);
//DENG_DECLARE_API(Uri);

DENG_API_EXCHANGE(
//DENG_GET_API(DE_API_BASE, Base);
//DENG_GET_API(DE_API_FILE_SYSTEM, F);
DENG_GET_API(DE_API_FILE_SYSTEM, F);
DENG_GET_API(DE_API_MAP, Map);
DENG_GET_API(DE_API_MATERIALS, Material);
DENG_GET_API(DE_API_MAP_EDIT, MPE);
//DENG_GET_API(DE_API_URI, Uri);
)
146 changes: 146 additions & 0 deletions doomsday/apps/plugins/importudmf/src/udmflex.cpp
@@ -0,0 +1,146 @@
/** @file udmflex.cpp UDMF lexical analyzer.
*
* @authors Copyright (c) 2016 Jaakko Keränen <jaakko.keranen@iki.fi>
*
* @par License
* GPL: http://www.gnu.org/licenses/gpl.html
*
* <small>This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. This program is distributed in the hope that it
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details. You should have received a copy of the GNU
* General Public License along with this program; if not, see:
* http://www.gnu.org/licenses</small>
*/

#include "udmflex.h"

using namespace de;

String const UDMFLex::NAMESPACE("namespace");
String const UDMFLex::LINEDEF("linedef");
String const UDMFLex::SIDEDEF("sidedef");
String const UDMFLex::VERTEX("vertex");
String const UDMFLex::SECTOR("sector");
String const UDMFLex::THING("thing");
String const UDMFLex::T_TRUE("true");
String const UDMFLex::T_FALSE("false");
String const UDMFLex::ASSIGN("=");
String const UDMFLex::BRACKET_OPEN("{");
String const UDMFLex::BRACKET_CLOSE("}");
String const UDMFLex::SEMICOLON(";");

UDMFLex::UDMFLex(String const &input)
: Lex(input, QChar('/'), DoubleCharComment)
{}

dsize UDMFLex::getExpression(TokenBuffer &output)
{
output.clear();

while (!atEnd())
{
skipWhite();

if (atEnd() || (output.size() && peek() == '}')) break;

// First character of the token.
QChar c = get();

output.newToken(lineNumber());
output.appendChar(c);

// Single-character tokens.
if (c == '{' || c == '}' || c == '=' || c == ';')
{
output.setType(c == '='? Token::OPERATOR : Token::LITERAL);
output.endToken();

if (output.latest().type() != Token::OPERATOR) break;
continue;
}

if (c == '"')
{
// Parse the string into one token.
output.setType(Token::LITERAL_STRING_QUOTED);
parseString(output);
output.endToken();
continue;
}

// Number literal?
if (parseLiteralNumber(c, output))
{
continue;
}

// Alphanumeric characters are joined into a token.
if (c == '_' || c.isLetter())
{
output.setType(Token::IDENTIFIER);

while (isAlphaNumeric((c = peek())))
{
output.appendChar(get());
}

// It might be that this is a keyword.
if (isKeyword(output.latest()))
{
output.setType(Token::KEYWORD);
}

output.endToken();
continue;
}
}

return output.size();
}

void UDMFLex::parseString(TokenBuffer &output)
{
ModeSpan readingMode(*this, SkipComments);

// The token already contains the first quote char.
// This will throw an exception if the string is unterminated.
forever
{
QChar c = get();
output.appendChar(c);
if (c == '"')
{
return;
}
if (c == '\\') // Escape.
{
output.appendChar(get());
}
}
}

bool UDMFLex::isKeyword(Token const &token)
{
static QVector<String> const keywordStr
{
NAMESPACE,
LINEDEF,
SIDEDEF,
VERTEX,
SECTOR,
THING,
T_TRUE,
T_FALSE,
};
foreach (auto const &kw, keywordStr)
{
if (!kw.compareWithoutCase(token.str()))
return true;
}
return false;
}

0 comments on commit 3c7abf9

Please sign in to comment.