Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
libdeng2|scriptsys: Added ScriptedInfo
ScriptedInfo combines Info (a structured declarative document) with Doomsday Script (procedural programming language). Each ScriptedInfo instance has its own local namespace where all the embedded scripts and expressions are processed/evaluated. The ".dei" (Doomsday Engine Info) file extension is used for scripted info files (cf. ".de" is used for plain Doomsday Script modules). Also added "test_info".
- Loading branch information
Showing
7 changed files
with
400 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
#include "scriptsys/scriptedinfo.h" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
/** @file scriptedinfo.h Info document tree with script context. | ||
* | ||
* @authors Copyright (c) 2013 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 LIBDENG2_SCRIPTEDINFO_H | ||
#define LIBDENG2_SCRIPTEDINFO_H | ||
|
||
#include "../Info" | ||
#include "../File" | ||
|
||
namespace de { | ||
|
||
/** | ||
* Info document tree with a script context and built-in support for handling | ||
* expressions and embedded scripts. | ||
* | ||
* Analogous to an HTML document with embedded JavaScript: Info acts as the | ||
* declarative, structured document and Doomsday Script is the procedural | ||
* programming language. | ||
* | ||
* An instance of ScriptedInfo represents an Info document. It has its own | ||
* private script execution context, in which expressions can be evaluated and | ||
* scripts run. After a ScriptedInfo has been parsed, all the embedded scripts | ||
* are run and the Info elements become variables and values in the local | ||
* namespace. | ||
* | ||
* Each block of a ScriptedInfo document has a couple of special elements | ||
* that alter how the block is processed: | ||
* | ||
* - The "condition" element that may be present in any block determines | ||
* whether the block is processed or skipped. The value of the "condition" | ||
* element is evaluated as a script, and if it evaluates to False, the | ||
* entire block is ignored. | ||
* | ||
* - The contents of any previously processed block (or any record available in | ||
* the namespace) can be copied with the special inheritance element | ||
* (named ":"): | ||
* <pre>type1 first-block { key = value } | ||
* type2 example-block : first-block {}</pre> | ||
* | ||
* Here @c first-block would be treated as a variable name in the document's | ||
* local namespace, referring to the block above, which has already been | ||
* added to the local namespace (elements are processed sequentially from | ||
* beginning to end). | ||
*/ | ||
class DENG2_PUBLIC ScriptedInfo | ||
{ | ||
public: | ||
ScriptedInfo(); | ||
|
||
void clear(); | ||
|
||
void parse(String const &source); | ||
|
||
void parse(File const &file); | ||
|
||
/** | ||
* Evaluates one or more statements and returns the result. All processing | ||
* is done in the document's local script context. Changes to the context's | ||
* global namespace are allowed and will remain in effect after the | ||
* evaluation finishes. | ||
* | ||
* @param source Statements or expression to evaluate. | ||
* | ||
* @return Result value. Caller gets ownership. | ||
*/ | ||
Value *evaluate(String const &source); | ||
|
||
private: | ||
DENG2_PRIVATE(d) | ||
}; | ||
|
||
} // namespace de | ||
|
||
#endif // LIBDENG2_SCRIPTEDINFO_H |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,206 @@ | ||
/** @file scriptedinfo.cpp Info document tree with script context. | ||
* | ||
* @authors Copyright (c) 2013 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 "de/ScriptedInfo" | ||
#include "de/Script" | ||
#include "de/Process" | ||
#include "de/ArrayValue" | ||
#include "de/RecordValue" | ||
|
||
namespace de { | ||
|
||
DENG2_PIMPL(ScriptedInfo) | ||
{ | ||
Info info; ///< Original full parsed contents. | ||
QScopedPointer<Script> script; ///< Current script being executed. | ||
Process process; ///< Execution context. | ||
String sourcePath; | ||
|
||
Instance(Public *i) : Base(i) | ||
{} | ||
|
||
void clear() | ||
{ | ||
info.clear(); | ||
process.clear(); | ||
script.reset(); | ||
} | ||
|
||
/** | ||
* Iterates through the parsed Info contents and processes each element. | ||
* Script blocks are parsed as Doomsday Script and executed in the local | ||
* process. Key/value elements become variables (and values) in the | ||
* process's global namespace. | ||
*/ | ||
void processAll() | ||
{ | ||
processBlock(info.root()); | ||
|
||
LOG_DEBUG("Processed contents:\n") << process.globals().asText(); | ||
} | ||
|
||
void processElement(Info::Element const *element) | ||
{ | ||
if(element->isBlock()) | ||
{ | ||
processBlock(*static_cast<Info::BlockElement const *>(element)); | ||
} | ||
else if(element->isKey()) | ||
{ | ||
processKey(*static_cast<Info::KeyElement const *>(element)); | ||
} | ||
else if(element->isList()) | ||
{ | ||
processList(*static_cast<Info::ListElement const *>(element)); | ||
} | ||
} | ||
|
||
void processBlock(Info::BlockElement const &block) | ||
{ | ||
if(Info::Element *condition = block.find("condition")) | ||
{ | ||
// Any block will be ignored if its condition is false. | ||
QScopedPointer<Value> result(evaluate(condition->values().first())); | ||
if(result.isNull() || result->isFalse()) | ||
{ | ||
return; | ||
} | ||
} | ||
|
||
if(block.blockType() == "script") | ||
{ | ||
DENG2_ASSERT(block.find("script") != 0); | ||
DENG2_ASSERT(process.state() == Process::Stopped); | ||
|
||
script.reset(new Script(block.find("script")->values().first())); | ||
script->setPath(sourcePath); // where the source comes from | ||
process.run(*script); | ||
process.execute(); | ||
} | ||
else | ||
{ | ||
// Block type placed into a special variable (only with named blocks, though). | ||
if(!block.name().isEmpty()) | ||
{ | ||
String varName = variableName(block).concatenatePath("__type__", '.'); | ||
process.globals().add(varName) = new TextValue(block.blockType()); | ||
} | ||
|
||
foreach(Info::Element const *sub, block.contentsInOrder()) | ||
{ | ||
if(sub->name() == "condition") | ||
{ | ||
// Skip special elements. | ||
continue; | ||
} | ||
if(sub->name() == ":" && !block.name().isEmpty()) | ||
{ | ||
// Inheritance. | ||
String target = sub->values().first(); | ||
process.globals().add(variableName(block).concatenatePath("__inherit__", '.')) = | ||
new TextValue(target); | ||
|
||
// Copy all present members of the target record. | ||
process.globals().subrecord(variableName(block)) | ||
.copyMembersFrom(process.globals()[target].value<RecordValue>().dereference()); | ||
continue; | ||
} | ||
processElement(sub); | ||
} | ||
} | ||
} | ||
|
||
String variableName(Info::Element const &element) | ||
{ | ||
String varName = element.name(); | ||
for(Info::BlockElement *b = element.parent(); b != 0; b = b->parent()) | ||
{ | ||
if(!b->name().isEmpty()) | ||
{ | ||
varName = b->name().concatenatePath(varName, '.'); | ||
} | ||
} | ||
return varName; | ||
} | ||
|
||
static bool isEvaluatable(String const &value) | ||
{ | ||
return value.startsWith('$'); | ||
} | ||
|
||
Value *evaluate(String const &source) | ||
{ | ||
script.reset(new Script(source)); | ||
process.run(*script); | ||
process.execute(); | ||
return process.context().evaluator().result().duplicate(); | ||
} | ||
|
||
Value *makeValue(String const &rawValue) | ||
{ | ||
if(isEvaluatable(rawValue)) | ||
{ | ||
return evaluate(rawValue.substr(1)); | ||
} | ||
return new TextValue(rawValue); | ||
} | ||
|
||
void processKey(Info::KeyElement const &key) | ||
{ | ||
QScopedPointer<Value> v(makeValue(key.value())); | ||
process.globals().add(variableName(key)) = v.take(); | ||
} | ||
|
||
void processList(Info::ListElement const &list) | ||
{ | ||
ArrayValue* av = new ArrayValue; | ||
foreach(QString v, list.values()) | ||
{ | ||
*av << makeValue(v); | ||
} | ||
process.globals().addArray(variableName(list), av); | ||
} | ||
}; | ||
|
||
ScriptedInfo::ScriptedInfo() : d(new Instance(this)) | ||
{} | ||
|
||
void ScriptedInfo::clear() | ||
{ | ||
d->clear(); | ||
} | ||
|
||
void ScriptedInfo::parse(String const &source) | ||
{ | ||
d->clear(); | ||
d->info.parse(source); | ||
d->processAll(); | ||
} | ||
|
||
void ScriptedInfo::parse(File const &file) | ||
{ | ||
d->sourcePath = file.path(); | ||
parse(String::fromUtf8(Block(file))); | ||
} | ||
|
||
Value *ScriptedInfo::evaluate(String const &source) | ||
{ | ||
return d->evaluate(source); | ||
} | ||
|
||
} // namespace de |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
/* | ||
* The Doomsday Engine Project | ||
* | ||
* Copyright (c) 2009-2013 Jaakko Keränen <jaakko.keranen@iki.fi> | ||
* | ||
* 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/>. | ||
*/ | ||
|
||
#include <de/TextApp> | ||
#include <de/LogBuffer> | ||
#include <de/ScriptedInfo> | ||
#include <de/FS> | ||
#include <QDebug> | ||
|
||
using namespace de; | ||
|
||
int main(int argc, char **argv) | ||
{ | ||
try | ||
{ | ||
TextApp app(argc, argv); | ||
app.initSubsystems(App::DisablePlugins); | ||
|
||
ScriptedInfo dei; | ||
dei.parse(app.fileSystem().find("test_info.dei")); | ||
} | ||
catch(Error const &err) | ||
{ | ||
qWarning() << err.asText(); | ||
} | ||
|
||
qDebug("Exiting main()..."); | ||
return 0; | ||
} |
Oops, something went wrong.