421 lines
13 KiB
C++
421 lines
13 KiB
C++
/****************************************************************************
|
|
**
|
|
** Copyright (C) 2006-2009 fullmetalcoder <fullmetalcoder@hotmail.fr>
|
|
**
|
|
** This file is part of the Edyuk project <http://edyuk.org>
|
|
**
|
|
** This file may be used under the terms of the GNU General Public License
|
|
** version 3 as published by the Free Software Foundation and appearing in the
|
|
** file GPL.txt included in the packaging of this file.
|
|
**
|
|
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
|
|
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
|
**
|
|
****************************************************************************/
|
|
|
|
/*!
|
|
\file xml2qnfa.cpp
|
|
\brief Implementation of the QNFA builder (fetch syntax engine data from
|
|
XML files)
|
|
*/
|
|
|
|
#include "qnfa.h"
|
|
#include "qnfadefinition.h"
|
|
|
|
#include "qformatscheme.h"
|
|
|
|
#include <QDomDocument>
|
|
#include <QDomElement>
|
|
#include <QDomText>
|
|
#include <QVariant>
|
|
|
|
/*
|
|
<QNFA>
|
|
<word></word>
|
|
<sequence></sequence>
|
|
|
|
<context>
|
|
<start></start>
|
|
<stop></stop>
|
|
<escape></escape>
|
|
|
|
...
|
|
|
|
</context>
|
|
</QNFA>
|
|
*/
|
|
|
|
QString *_singleLineCommentTarget = 0;
|
|
|
|
bool stringToBool(const QString &s, bool previous) {
|
|
if (s.isEmpty())
|
|
return previous;
|
|
|
|
return ((s == "true") || (s == "enabled")) ||
|
|
((s != "false") && (s != "disabled") && QVariant(s).toBool());
|
|
}
|
|
|
|
int pid(const QString &s, QHash<QString, int> &pids) {
|
|
if (pids.contains(s))
|
|
return pids.value(s);
|
|
|
|
int id = (pids.count() + 1) << 12;
|
|
|
|
pids[s] = id;
|
|
|
|
return id;
|
|
}
|
|
|
|
int action(QDomElement c, QFormatScheme *f, QHash<QString, int> &pids,
|
|
int fid = 0) {
|
|
QString paren, spid, spt, sfid;
|
|
|
|
sfid = c.attribute("format");
|
|
|
|
if (sfid.length()) {
|
|
fid |= QNFAAction::Highlight | QNFAAction::format(f->id(sfid));
|
|
}
|
|
|
|
paren = c.attribute("parenthesis");
|
|
|
|
if (paren.length()) {
|
|
spid = paren.section(':', 0, -2);
|
|
spt = paren.section(':', -1, -1);
|
|
|
|
if (spt.endsWith("@nomatch")) {
|
|
spt.chop(8);
|
|
} else {
|
|
fid |= QNFAAction::MatchParen;
|
|
}
|
|
|
|
if (spid.length()) {
|
|
/*qDebug("paren : [%s|%s] => 0x%x",
|
|
qPrintable(spid), qPrintable(spt),
|
|
(spt == "open" ? QNFAAction::ParenOpen :
|
|
QNFAAction::ParenClose) | pid(spid, pids));
|
|
*/
|
|
|
|
if (spt == "open")
|
|
fid |= QNFAAction::ParenOpen;
|
|
else if (spt == "close")
|
|
fid |= QNFAAction::ParenClose;
|
|
else if (spt == "boundary")
|
|
fid |= QNFAAction::ParenOpen | QNFAAction::ParenClose;
|
|
|
|
fid |= QNFAAction::parenthesis(pid(spid, pids));
|
|
|
|
/*
|
|
qDebug("paren : [%s|%s] => 0x%x",
|
|
qPrintable(spid), qPrintable(spt),
|
|
fid & QNFAAction::ParenMask);
|
|
*/
|
|
}
|
|
}
|
|
|
|
if (stringToBool(c.attribute("indent"), false))
|
|
fid |= QNFAAction::Indent;
|
|
|
|
if (stringToBool(c.attribute("fold"), false))
|
|
fid |= QNFAAction::Fold;
|
|
|
|
// TODO : determine ambiguity automatically
|
|
if (stringToBool(c.attribute("ambiguous"), false))
|
|
fid |= QNFAAction::Ambiguous;
|
|
|
|
return fid;
|
|
}
|
|
|
|
void copy(const QCharTreeLevel &src, QCharTreeLevel &dest) {
|
|
QCharTreeLevel::const_iterator it = src.constBegin();
|
|
|
|
while (it != src.constEnd()) {
|
|
// qDebug("copying char tree level %c", QChar(it.key()).toLatin1());
|
|
|
|
if (!dest.contains(it.key()))
|
|
dest[it.key()] = *it;
|
|
else
|
|
copy(it->next, dest[it.key()].next);
|
|
|
|
++it;
|
|
}
|
|
}
|
|
|
|
void embed(QNFA *src, QNFA *dest, qsizetype idx = 0) {
|
|
QNFA *nfa;
|
|
const auto n = src->out.branch->count();
|
|
|
|
// dest->out.branch->alloc(idx, n);
|
|
|
|
// qDebug("\tembedding %i children", n);
|
|
|
|
for (size_t i = 0; i < n; ++i) {
|
|
nfa = src->out.branch->at(i);
|
|
|
|
if (nfa->type & Reserved)
|
|
continue;
|
|
|
|
// qDebug("\t\teffectively embedding 0x%x at %i", nfa, idx);
|
|
dest->out.branch->insert(idx++, nfa);
|
|
}
|
|
|
|
copy(src->tree, dest->tree);
|
|
}
|
|
|
|
void fillContext(QNFA *cxt, QDomElement e, QFormatScheme *f,
|
|
QHash<QString, int> &pids, bool cs);
|
|
void fillContext(QNFA *cxt, QDomNodeList l, QFormatScheme *f,
|
|
QHash<QString, int> &pids, bool cs);
|
|
|
|
void addToContext(QNFA *cxt, QDomElement c, int fid, QFormatScheme *f,
|
|
QHash<QString, int> &pids, const QStringList &pref,
|
|
const QStringList &suff, bool cs) {
|
|
QString tag = c.tagName();
|
|
|
|
if (!c.hasChildNodes() && tag != "embed")
|
|
return;
|
|
|
|
cs = stringToBool(c.attribute("caseSensitive"), cs);
|
|
|
|
if ((tag == "start") || (tag == "stop") || (tag == "escape")) {
|
|
return;
|
|
} else if (tag == "word") {
|
|
// qDebug("adding word : %s",
|
|
// qPrintable(c.firstChild().toText().data()));
|
|
const QString value = c.firstChild().toText().data();
|
|
|
|
if (pref.isEmpty() && suff.isEmpty()) {
|
|
addWord(cxt, value, fid, cs);
|
|
} else if (pref.count()) {
|
|
foreach (const QString &p, pref) {
|
|
if (suff.isEmpty()) {
|
|
addWord(cxt, p + value, fid, cs);
|
|
} else {
|
|
foreach (const QString &s, suff) {
|
|
addWord(cxt, p + value + s, fid, cs);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
foreach (const QString &s, suff) {
|
|
addWord(cxt, value + s, fid, cs);
|
|
}
|
|
}
|
|
} else if (tag == "sequence") {
|
|
const QString value = c.firstChild().toText().data();
|
|
|
|
// qDebug("adding sequence : %s [0x%x]", qPrintable(value), cxt);
|
|
if (pref.isEmpty() && suff.isEmpty()) {
|
|
addSequence(cxt, value, fid, cs);
|
|
} else if (pref.count()) {
|
|
foreach (const QString &p, pref) {
|
|
if (suff.isEmpty()) {
|
|
addSequence(cxt, p + value, fid, cs);
|
|
} else {
|
|
foreach (const QString &s, suff) {
|
|
addSequence(cxt, p + value + s, fid, cs);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
foreach (const QString &s, suff) {
|
|
addSequence(cxt, value + s, fid, cs);
|
|
}
|
|
}
|
|
} else if (tag == "list") {
|
|
QDomNodeList children = c.childNodes();
|
|
QStringList prefixes, suffixes;
|
|
|
|
// qDebug("starting list");
|
|
for (int j = 0; j < children.count(); ++j) {
|
|
QDomElement cc = children.at(j).toElement();
|
|
|
|
if (cc.isNull())
|
|
continue;
|
|
|
|
const QString role = cc.tagName();
|
|
const QString value = cc.firstChild().toText().data();
|
|
|
|
if (role == "prefix")
|
|
prefixes << value;
|
|
else if (role == "suffix")
|
|
suffixes << value;
|
|
else
|
|
addToContext(cxt, cc, action(cc, f, pids, fid), f, pids,
|
|
prefixes, suffixes, cs);
|
|
}
|
|
// qDebug("ending list");
|
|
|
|
} else if (tag == "embed") {
|
|
QNFADefinition::addEmbedRequest(c.attribute("target"), cxt);
|
|
} else if (tag == "context") {
|
|
QNFA *nfa, *start = 0, *hstart = 0, *cstart = 0, *stop = 0, *hstop = 0,
|
|
*escape = 0, *hescape = 0;
|
|
|
|
QList<QNFA *> lStart, lStop, lEscape;
|
|
|
|
QString attr;
|
|
int defact = action(c, f, pids);
|
|
QDomNodeList children = c.childNodes();
|
|
|
|
QString _id = c.attribute("id");
|
|
bool trans = stringToBool(c.attribute("transparency"), false);
|
|
bool stay = stringToBool(c.attribute("stayOnLine"), false);
|
|
|
|
for (int j = 0; j < children.count(); ++j) {
|
|
QDomElement child = children.at(j).toElement();
|
|
|
|
if (child.isNull() || !child.hasChildNodes())
|
|
continue;
|
|
|
|
bool tcs = stringToBool(child.attribute("caseSensitive"), cs);
|
|
|
|
int act = action(child, f, pids);
|
|
|
|
if (!(act & QNFAAction::Highlight))
|
|
act |= QNFAAction::Highlight | QNFAAction::format(defact);
|
|
|
|
const QString role = child.tagName();
|
|
const QString value = child.firstChild().toText().data();
|
|
|
|
if (role == "start") {
|
|
start = sequence(value, &hstart, tcs);
|
|
start->actionid = act;
|
|
// start->type |= Reserved;
|
|
lStart << start;
|
|
|
|
if (!cstart) {
|
|
cstart = new QNFA;
|
|
cstart->type = ContextBegin | (stay ? StayOnLine : 0);
|
|
cstart->actionid = defact;
|
|
cstart->out.branch = new QNFABranch;
|
|
|
|
// only the first sequence comes
|
|
if (_singleLineCommentTarget && (_id == "comment/single"))
|
|
*_singleLineCommentTarget = value;
|
|
}
|
|
|
|
hstart->out.next = cstart;
|
|
hstart->actionid = act;
|
|
// hstart = nfa;
|
|
|
|
// qDebug("cxt start seq : %s [0x%x, 0x%x]", qPrintable(value),
|
|
// start, nfa);
|
|
|
|
} else if (role == "stop") {
|
|
stop = sequence(value, &hstop, tcs);
|
|
stop->actionid = act;
|
|
stop->type |= Reserved;
|
|
lStop << stop;
|
|
|
|
nfa = new QNFA;
|
|
nfa->type = ContextEnd;
|
|
nfa->actionid = act;
|
|
// nfa->out.branch = new QNFABranch;
|
|
|
|
hstop->out.next = nfa;
|
|
hstop->actionid = act;
|
|
hstop = nfa;
|
|
|
|
attr = child.attribute("exclusive");
|
|
|
|
if (attr.isEmpty() || (attr == "true") || attr.toUInt())
|
|
hstop->type |= Exclusive;
|
|
|
|
// qDebug("cxt stop seq : %s [0x%x]", qPrintable(value), act);
|
|
|
|
} else if (role == "escape") {
|
|
|
|
escape = sequence(value, &hescape, tcs);
|
|
escape->type |= Reserved;
|
|
lEscape << escape;
|
|
|
|
nfa = new QNFA;
|
|
nfa->type = EscapeSeq;
|
|
nfa->actionid = action(child, f, pids);
|
|
// nfa->out.branch = new QNFABranch;
|
|
|
|
hescape->out.next = nfa;
|
|
// hescape = nfa;
|
|
}
|
|
}
|
|
|
|
if (hstart) {
|
|
// qDebug("starting cxt %s:0x%x [0x%x]",
|
|
// qPrintable(c.attribute("id")), cstart, cxt);
|
|
|
|
foreach (escape, lEscape) {
|
|
// cstart->type |= Escaped;
|
|
addNFA(cstart, escape);
|
|
}
|
|
|
|
// qDebug("after esc : %i", cstart->out.branch->count());
|
|
|
|
foreach (stop, lStop)
|
|
addNFA(cstart, stop);
|
|
|
|
// qDebug("after stop : %i", cstart->out.branch->count());
|
|
} else {
|
|
cstart = new QNFA;
|
|
cstart->type = ContextBegin | (stay ? StayOnLine : 0);
|
|
cstart->actionid = defact;
|
|
cstart->out.branch = new QNFABranch;
|
|
}
|
|
|
|
fillContext(cstart, c, f, pids, cs);
|
|
|
|
if (c.hasAttribute("id")) {
|
|
QNFADefinition::addContext(
|
|
c.ownerDocument().documentElement().attribute("language") +
|
|
":" + _id,
|
|
cstart);
|
|
|
|
} else {
|
|
// qDebug("unregistered context");
|
|
}
|
|
|
|
// qDebug("after sub : %i", cstart->out.branch->count());
|
|
|
|
if (trans) {
|
|
QNFADefinition::shareEmbedRequests(cxt, cstart,
|
|
cstart->out.branch->length());
|
|
embed(cxt, cstart, cstart->out.branch->length());
|
|
}
|
|
|
|
if (hstart) {
|
|
foreach (start, lStart)
|
|
addNFA(cxt, start);
|
|
|
|
// qDebug("ending cxt");
|
|
} else {
|
|
}
|
|
|
|
// fillContext(subcxt, c, f, pids);
|
|
} else {
|
|
// qDebug("unhandled tag : %s", qPrintable(tag));
|
|
}
|
|
}
|
|
|
|
void fillContext(QNFA *cxt, QDomElement e, QFormatScheme *f,
|
|
QHash<QString, int> &pids, bool cs) {
|
|
cs = stringToBool(e.attribute("caseSensitive"), cs);
|
|
|
|
fillContext(cxt, e.childNodes(), f, pids, cs);
|
|
}
|
|
|
|
void fillContext(QNFA *cxt, QDomNodeList l, QFormatScheme *f,
|
|
QHash<QString, int> &pids, bool cs) {
|
|
// qDebug("filling context from %i nodes", l.count());
|
|
|
|
for (int i = 0; i < l.count(); i++) {
|
|
QDomElement c = l.at(i).toElement();
|
|
|
|
if (c.isNull())
|
|
continue;
|
|
|
|
addToContext(cxt, c, action(c, f, pids), f, pids, QStringList(),
|
|
QStringList(), cs);
|
|
}
|
|
|
|
// qDebug("context filled");
|
|
}
|