8#include <common_helpers_p.h>
9#include <ktranscript_p.h>
11#include <ktranscript_export.h>
22#include <QJSValueIterator>
25#include <QStandardPaths>
38class KTranscriptImp :
public KTranscript
42 ~KTranscriptImp()
override;
55 bool &fallback)
override;
64 void setupInterpreter(
const QString &lang);
72class Scriptface :
public QObject
108 QJSValue load(
const QJSValueList &names);
109 QJSValue loadProps(
const QJSValueList &names);
130 bool *fallbackRequest;
146 struct UnparsedPropInfo {
147 QFile *pmapFile =
nullptr;
162#define DBGP "KTranscript: "
163void dbgout(
const char *str)
166 fprintf(stderr, DBGP
"%s\n", str);
172void dbgout(
const char *str,
const T1 &a1)
175 fprintf(stderr, DBGP
"%s\n",
QString::fromUtf8(str).arg(a1).toLocal8Bit().data());
181template<
typename T1,
typename T2>
182void dbgout(
const char *str,
const T1 &a1,
const T2 &a2)
185 fprintf(stderr, DBGP
"%s\n",
QString::fromUtf8(str).arg(a1).arg(a2).toLocal8Bit().data());
192template<
typename T1,
typename T2,
typename T3>
193void dbgout(
const char *str,
const T1 &a1,
const T2 &a2,
const T3 &a3)
196 fprintf(stderr, DBGP
"%s\n",
QString::fromUtf8(str).arg(a1).arg(a2).arg(a3).toLocal8Bit().data());
205#define WARNP "KTranscript: "
206void warnout(
const char *str)
208 fprintf(stderr, WARNP
"%s\n", str);
211void warnout(
const char *str,
const T1 &a1)
213 fprintf(stderr, WARNP
"%s\n",
QString::fromUtf8(str).arg(a1).toLocal8Bit().data());
224 return QStringLiteral(
"Error: %1").arg(message.
toString());
229 return QStringLiteral(
"Caught exception: %1").
arg(strexpt);
235int countLines(
const QString &s,
int p)
239 for (
int i = 0; i < p && i < len; ++i) {
260 for (
int i = 0; i < len; ++i) {
270 key = removeAcceleratorMarker(key);
293 while (is < len && raw[is].isSpace() && raw[is] !=
QLatin1Char(
'\n')) {
301 while (ie >= 0 && raw[ie].isSpace() && raw[ie] !=
QLatin1Char(
'\n')) {
308 return raw.
mid(is + 1, ie - is - 1);
348 while (!stream.atEnd()) {
349 QString line = stream.readLine();
371 configGroup = config.
find(group);
372 if (configGroup == config.
end()) {
401 return engine->
evaluate(QStringLiteral(
"new Error(%1)").arg(message));
404 qCritical() <<
"Script error" << message;
408#ifdef KTRANSCRIPT_TESTBUILD
412static KTranscriptImp *s_transcriptInstance =
nullptr;
414KTranscriptImp *globalKTI()
416 return s_transcriptInstance;
419KTranscript *autotestCreateKTranscriptImp()
421 Q_ASSERT(s_transcriptInstance ==
nullptr);
422 s_transcriptInstance =
new KTranscriptImp;
423 return s_transcriptInstance;
426void autotestDestroyKTranscriptImp()
428 Q_ASSERT(s_transcriptInstance !=
nullptr);
429 delete s_transcriptInstance;
430 s_transcriptInstance =
nullptr;
437Q_GLOBAL_STATIC(KTranscriptImp, globalKTI)
439KTRANSCRIPT_EXPORT KTranscript *load_transcript()
449KTranscriptImp::KTranscriptImp()
457 config = readConfig(tsConfigPath);
460KTranscriptImp::~KTranscriptImp()
488 if (geteuid() == 0 && getuid() != 0) {
492 error =
"Security block: trying to execute a script in suid environment.";
499 loadModules(mods, error);
501 if (!
error.isEmpty()) {
511 setupInterpreter(lang);
515 Scriptface *sface = m_sface[lang];
521 sface->msgcontext = &msgctxt;
522 sface->dyncontext = &dynctxt;
523 sface->msgId = &msgid;
524 sface->subList = &subs;
525 sface->valList = &vals;
526 sface->ftrans = &ftrans;
527 sface->fallbackRequest = &fallback;
531 int argc = argv.
size();
538 QString funcName = argv[0].toString();
539 if (!sface->funcs.
contains(funcName)) {
540 error = QStringLiteral(
"Unregistered call to '%1'.").arg(funcName);
544 QJSValue func = sface->funcs[funcName];
545 QJSValue fval = sface->fvals[funcName];
549 currentModulePath = sface->fpaths[funcName];
552 QJSValueList arglist;
554 for (
int i = 1; i < argc; ++i) {
579 error = QStringLiteral(
"Non-string return value: %1").arg(strval);
585 error = expt2str(val);
600 Scriptface *sface = m_sface[lang];
602 return sface->nameForalls;
615 setupInterpreter(mlang);
622 modErrors.
append(QStringLiteral(
"Funny module path '%1', skipping.").arg(mpath));
625 currentModulePath = mpath.
left(posls);
634 m_sface[mlang]->load(alist);
638 currentModulePath.
clear();
640 for (
const QString &merr : std::as_const(modErrors)) {
646void KTranscriptImp::setupInterpreter(
const QString &lang)
652 Scriptface *sface =
new Scriptface(config[lang]);
655 m_sface[lang] = sface;
663 , fallbackRequest(nullptr)
666 QJSValue object = scriptEngine->newQObject(
this);
667 scriptEngine->globalObject().
setProperty(QStringLiteral(SFNAME),
object);
668 scriptEngine->evaluate(QStringLiteral(
"Ts.acall = function() { return Ts.acallInternal(Array.prototype.slice.call(arguments)); };"));
671Scriptface::~Scriptface()
673 qDeleteAll(loadedPmapHandles);
677void Scriptface::put(
const QString &propertyName,
const QJSValue &value)
681 internalObject = scriptEngine->
newObject();
693#define SPREF(X) QString::fromLatin1(SFNAME "." X)
695#define SPREF(X) QStringLiteral(SFNAME "." X)
707 if (!
name.isString()) {
708 return throwError(scriptEngine, SPREF(
"setcall: expected string as first argument"));
711 return throwError(scriptEngine, SPREF(
"setcall: expected function as second argument"));
714 return throwError(scriptEngine, SPREF(
"setcall: expected object or null as third argument"));
722 put(QStringLiteral(
"#:f<%1>").arg(qname), func);
723 put(QStringLiteral(
"#:o<%1>").arg(qname), fval);
727 fpaths[qname] = globalKTI()->currentModulePath;
742 return throwError(scriptEngine, SPREF(
"acall: expected at least one argument (call name)"));
744 if (!it.value().isString()) {
745 return throwError(scriptEngine, SPREF(
"acall: expected string as first argument (call name)"));
748 QString callname = it.value().toString();
750 return throwError(scriptEngine, SPREF(
"acall: unregistered call to '%1'").arg(callname));
757 globalKTI()->currentModulePath = fpaths[callname];
760 QJSValueList arglist;
762 arglist.append(it.value());
778 if (!
name.isString()) {
779 return throwError(scriptEngine, SPREF(
"setcallForall: expected string as first argument"));
782 return throwError(scriptEngine, SPREF(
"setcallForall: expected function as second argument"));
785 return throwError(scriptEngine, SPREF(
"setcallForall: expected object or null as third argument"));
793 put(QStringLiteral(
"#:fall<%1>").arg(qname), func);
794 put(QStringLiteral(
"#:oall<%1>").arg(qname), fval);
798 fpaths[qname] = globalKTI()->currentModulePath;
801 nameForalls.
append(qname);
808 if (fallbackRequest) {
809 *fallbackRequest =
true;
822 return throwError(scriptEngine, SPREF(
"subs: expected number as first argument"));
826 if (i < 0 || i >= subList->
size()) {
827 return throwError(scriptEngine, SPREF(
"subs: index out of range"));
836 return throwError(scriptEngine, SPREF(
"vals: expected number as first argument"));
840 if (i < 0 || i >= valList->
size()) {
841 return throwError(scriptEngine, SPREF(
"vals: index out of range"));
855 auto valIt = dyncontext->
constFind(qkey);
856 if (valIt != dyncontext->
constEnd()) {
877void Scriptface::dbgputs(
const QString &qstr)
879 dbgout(
"[JS-debug] %1", qstr);
882void Scriptface::warnputs(
const QString &qstr)
884 warnout(
"[JS-warning] %1", qstr);
895 return throwError(scriptEngine, SPREF(
"normKey: expected string as argument"));
906 return loadProps(fnames);
909QJSValue Scriptface::loadProps(
const QJSValueList &fnames)
911 if (globalKTI()->currentModulePath.isEmpty()) {
912 return throwError(scriptEngine, SPREF(
"loadProps: no current module path, aiiie..."));
915 for (
int i = 0; i < fnames.size(); ++i) {
916 if (!fnames[i].isString()) {
917 return throwError(scriptEngine, SPREF(
"loadProps: expected string as file name"));
921 for (
int i = 0; i < fnames.size(); ++i) {
922 QString qfname = fnames[i].toString();
928 bool haveCompiled =
true;
929 QFile file_check(qfpath);
931 haveCompiled =
false;
933 QFile file_check(qfpath);
935 return throwError(scriptEngine, SPREF(
"loadProps: cannot read map '%1'").arg(qfpath));
941 if (!loadedPmapPaths.
contains(qfpath)) {
944 errorString = loadProps_bin(qfpath);
946 errorString = loadProps_text(qfpath);
949 return throwError(scriptEngine, errorString);
951 dbgout(
"Loaded property map: %1", qfpath);
952 loadedPmapPaths.
insert(qfpath);
962 return throwError(scriptEngine, SPREF(
"getProp: expected string as first argument"));
965 return throwError(scriptEngine, SPREF(
"getProp: expected string as second argument"));
971 props = resolveUnparsedProps(qphrase);
986 return throwError(scriptEngine, SPREF(
"setProp: expected string as first argument"));
989 return throwError(scriptEngine, SPREF(
"setProp: expected string as second argument"));
992 return throwError(scriptEngine, SPREF(
"setProp: expected string as third argument"));
999 phraseProps[qphrase][qprop] = qvalue;
1003static QString toCaseFirst(
const QString &qstr,
int qnalt,
bool toupper)
1006 static const int hlen = 2;
1011 const int len = qstr.
length();
1013 int remainingAlts = 0;
1014 bool checkCase =
true;
1020 if (qnalt && !remainingAlts &&
QStringView(qstr).mid(i, hlen) == head) {
1029 remainingAlts = qnalt;
1031 }
else if (remainingAlts && c == altSep) {
1036 }
else if (checkCase && c.
isLetter()) {
1050 if (numChcased > 0 && remainingAlts == 0) {
1064 return throwError(scriptEngine, SPREF(
"toUpperFirst: expected string as first argument"));
1067 return throwError(scriptEngine, SPREF(
"toUpperFirst: expected number as second argument"));
1073 QString qstruc = toCaseFirst(qstr, qnalt,
true);
1081 return throwError(scriptEngine, SPREF(
"toLowerFirst: expected string as first argument"));
1084 return throwError(scriptEngine, SPREF(
"toLowerFirst: expected number as second argument"));
1090 QString qstrlc = toCaseFirst(qstr, qnalt,
false);
1098 return throwError(scriptEngine, QStringLiteral(
"getConfString: expected string as first argument"));
1101 return throwError(scriptEngine, SPREF(
"getConfString: expected string as second argument (when given)"));
1116 return throwError(scriptEngine, SPREF(
"getConfBool: expected string as first argument"));
1119 return throwError(scriptEngine, SPREF(
"getConfBool: expected boolean as second argument (when given)"));
1125 falsities.
append(QStringLiteral(
"no"));
1126 falsities.
append(QStringLiteral(
"false"));
1142 return throwError(scriptEngine,
1143 SPREF(
"getConfNumber: expected string "
1144 "as first argument"));
1147 return throwError(scriptEngine,
1148 SPREF(
"getConfNumber: expected number "
1149 "as second argument (when given)"));
1157 double qnum = qval.
toDouble(&convOk);
1169QJSValue Scriptface::load(
const QJSValueList &fnames)
1171 if (globalKTI()->currentModulePath.isEmpty()) {
1172 return throwError(scriptEngine, SPREF(
"load: no current module path, aiiie..."));
1175 for (
int i = 0; i < fnames.size(); ++i) {
1176 if (!fnames[i].isString()) {
1177 return throwError(scriptEngine, SPREF(
"load: expected string as file name"));
1181 for (
int i = 0; i < fnames.size(); ++i) {
1182 QString qfname = fnames[i].toString();
1187 return throwError(scriptEngine, SPREF(
"load: cannot read file '%1'").arg(qfpath));
1191 QString source = stream.readAll();
1207 return throwError(scriptEngine, QStringLiteral(
"at %1:%2: %3").arg(qfpath, line, msg));
1209 dbgout(
"Loaded module: %1", qfpath);
1218 return SPREF(
"loadProps_text: cannot read file '%1'").arg(fpath);
1227 enum { s_nextEntry, s_nextKey, s_nextValue };
1231 int state = s_nextEntry;
1237 int i_checkpoint = i;
1239 if (state == s_nextEntry) {
1240 while (s[i].isSpace()) {
1243 goto END_PROP_PARSE;
1246 if (i + 1 >= slen) {
1247 return SPREF(
"loadProps_text: unexpected end of file in %1").arg(fpath);
1252 prop_sep = s[i + 1];
1254 return SPREF(
"loadProps_text: separator characters must not be letters at %1:%2").arg(fpath).arg(countLines(s, i));
1269 goto END_PROP_PARSE;
1273 }
else if (state == s_nextKey) {
1276 while (s[i] != key_sep && s[i] != prop_sep) {
1279 goto END_PROP_PARSE;
1282 if (s[i] == key_sep) {
1285 pkey = normKeystr(s.
mid(ip, i - ip),
false);
1288 state = s_nextValue;
1300 if (ekeys.
size() < 1) {
1301 return SPREF(
"loadProps_text: no entry key for entry ending at %1:%2").arg(fpath).arg(countLines(s, i));
1306 for (
const QByteArray &ekey : std::as_const(ekeys)) {
1307 phraseProps[ekey] = props;
1311 state = s_nextEntry;
1314 }
else if (state == s_nextValue) {
1317 while (s[i] != prop_sep) {
1320 goto END_PROP_PARSE;
1322 if (s[i] == key_sep) {
1323 return SPREF(
"loadProps_text: property separator inside property value at %1:%2").arg(fpath).arg(countLines(s, i));
1333 return SPREF(
"loadProps: internal error 10 at %1:%2").arg(fpath).arg(countLines(s, i));
1337 if (i == i_checkpoint || i >= slen) {
1338 return SPREF(
"loadProps: internal error 20 at %1:%2").arg(fpath).arg(countLines(s, i));
1344 if (state != s_nextEntry) {
1345 return SPREF(
"loadProps: unexpected end of file in %1").arg(fpath);
1356static int bin_read_int_nbytes(
const char *fc, qlonglong len, qlonglong &pos,
int nbytes)
1358 if (pos + nbytes > len) {
1362 T num = qFromBigEndian<T>((uchar *)fc + pos);
1368static quint64 bin_read_int64(
const char *fc, qlonglong len, qlonglong &pos)
1370 return bin_read_int_nbytes<quint64>(fc, len, pos, 8);
1374static quint32 bin_read_int(
const char *fc, qlonglong len, qlonglong &pos)
1376 return bin_read_int_nbytes<quint32>(fc, len, pos, 4);
1383static QByteArray bin_read_string(
const char *fc, qlonglong len, qlonglong &pos)
1387 int nbytes = bin_read_int(fc, len, pos);
1391 if (nbytes < 0 || pos + nbytes > len) {
1404 return SPREF(
"loadProps: cannot read file '%1'").arg(fpath);
1408 file.read(head.data(), head.size());
1412 if (head ==
"TSPMAP00") {
1413 return loadProps_bin_00(fpath);
1414 }
else if (head ==
"TSPMAP01") {
1415 return loadProps_bin_01(fpath);
1417 return SPREF(
"loadProps: unknown version of compiled map '%1'").arg(fpath);
1425 return SPREF(
"loadProps: cannot read file '%1'").arg(fpath);
1429 const char *fc = fctmp.
data();
1430 const int fclen = fctmp.
size();
1438 if (head !=
"TSPMAP00") {
1439 goto END_PROP_PARSE;
1444 nentries = bin_read_int(fc, fclen, pos);
1446 goto END_PROP_PARSE;
1450 for (
int i = 0; i < nentries; ++i) {
1453 int nekeys = bin_read_int(fc, fclen, pos);
1455 goto END_PROP_PARSE;
1458 for (
int j = 0; j < nekeys; ++j) {
1459 QByteArray ekey = bin_read_string(fc, fclen, pos);
1461 goto END_PROP_PARSE;
1469 int nprops = bin_read_int(fc, fclen, pos);
1471 goto END_PROP_PARSE;
1473 for (
int j = 0; j < nprops; ++j) {
1474 QByteArray pkey = bin_read_string(fc, fclen, pos);
1476 goto END_PROP_PARSE;
1478 QByteArray pval = bin_read_string(fc, fclen, pos);
1480 goto END_PROP_PARSE;
1487 for (
const QByteArray &ekey : std::as_const(ekeys)) {
1488 phraseProps[ekey] = props;
1495 return SPREF(
"loadProps: corrupt compiled map '%1'").arg(fpath);
1505 return SPREF(
"loadProps: cannot read file '%1'").arg(fpath);
1512 fstr = file->
read(8 + 4 + 8);
1516 if (head !=
"TSPMAP01") {
1517 return SPREF(
"loadProps: corrupt compiled map '%1'").arg(fpath);
1519 quint32 numekeys = bin_read_int(fstr, fstr.
size(), pos);
1520 quint64 lenekeys = bin_read_int64(fstr, fstr.
size(), pos);
1523 fstr = file->
read(lenekeys);
1525 for (quint32 i = 0; i < numekeys; ++i) {
1526 QByteArray ekey = bin_read_string(fstr, lenekeys, pos);
1527 quint64 offset = bin_read_int64(fstr, lenekeys, pos);
1528 phraseUnparsedProps[ekey] = {file, offset};
1534 loadedPmapHandles.
insert(file);
1540 auto [file, offset] = phraseUnparsedProps.
value(phrase);
1542 if (file && file->
seek(offset)) {
1545 quint32 numpkeys = bin_read_int(fstr, fstr.
size(), pos);
1546 quint32 lenpkeys = bin_read_int(fstr, fstr.
size(), pos);
1547 fstr = file->
read(lenpkeys);
1549 for (quint32 i = 0; i < numpkeys; ++i) {
1550 QByteArray pkey = bin_read_string(fstr, lenpkeys, pos);
1551 QByteArray pval = bin_read_string(fstr, lenpkeys, pos);
1554 phraseProps[phrase] = props;
1555 phraseUnparsedProps.
remove(phrase);
1560#include "ktranscript.moc"
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
QString name(StandardAction id)
QCA_EXPORT void setProperty(const QString &name, const QVariant &value)
bool isEmpty() const const
QByteArray left(qsizetype len) const const
qsizetype size() const const
QByteArray toLower() const const
bool isLetter(char32_t ucs4)
bool isSpace(char32_t ucs4)
char32_t toLower(char32_t ucs4)
char32_t toUpper(char32_t ucs4)
bool open(FILE *fh, OpenMode mode, FileHandleFlags handleFlags)
virtual bool seek(qint64 pos) override
const_iterator constEnd() const const
const_iterator constFind(const Key &key) const const
bool contains(const Key &key) const const
iterator find(const Key &key)
iterator insert(const Key &key, const T &value)
bool isEmpty() const const
bool remove(const Key &key)
void reserve(qsizetype size)
T value(const Key &key) const const
QByteArray read(qint64 maxSize)
QJSValue evaluate(const QString &program, const QString &fileName, int lineNumber, QStringList *exceptionStackTrace)
QJSValue globalObject() const const
QJSValue toScriptValue(const T &value)
QJSValue callWithInstance(const QJSValue &instance, const QJSValueList &args) const const
bool isBool() const const
bool isCallable() const const
bool isError() const const
bool isNull() const const
bool isNumber() const const
bool isObject() const const
bool isString() const const
bool isUndefined() const const
QJSValue property(const QString &name) const const
void setProperty(const QString &name, const QJSValue &value)
qint32 toInt() const const
double toNumber() const const
QString toString() const const
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
bool isEmpty() const const
void reserve(qsizetype size)
qsizetype size() const const
QObject * parent() const const
bool contains(const QSet< T > &other) const const
iterator insert(const T &value)
QString locate(StandardLocation type, const QString &fileName, LocateOptions options)
QString & append(QChar ch)
QString arg(Args &&... args) const const
QString fromUtf8(QByteArrayView str)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype lastIndexOf(QChar ch, Qt::CaseSensitivity cs) const const
QString left(qsizetype n) const const
qsizetype length() const const
QString mid(qsizetype position, qsizetype n) const const
QString number(double n, char format, int precision)
double toDouble(bool *ok) const const
QString toLower() const const
QByteArray toUtf8() const const
QString trimmed() const const
void truncate(qsizetype position)
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
QStringView left(qsizetype length) const const
QStringView mid(qsizetype start, qsizetype length) const const
bool isEmpty() const const
QString toString() const const
QStringView trimmed() const const
bool toBool() const const
double toDouble(bool *ok) const const
QString toString() const const
int userType() const const