45 enum class StringType { NoString, String, FormatString, RawString };
48 PythonParser(Translator &translator,
const QString &fileName,
bool &error, ConversionData &cd)
49 : tor(translator), m_cd(cd)
52 const auto *fileNameC =
reinterpret_cast<
const wchar_t *>(fileName.utf16());
53 error = _wfopen_s(&yyInFile, fileNameC, L"r") != 0;
55 const QByteArray fileNameC = QFile::encodeName(fileName);
56 yyInFile = std::fopen(fileNameC.constData(),
"r");
57 error = yyInFile ==
nullptr;
60 startTokenizer(fileName);
64
65
66
67
68
69
70
71
72 void parse(
const QByteArray &initialContext = {},
const QByteArray &defaultContext = {})
80 while (yyTok != Tok_Eof) {
84 if (yyIndentationSize < 0 && yyContinuousSpaceCount > 0)
85 yyIndentationSize = yyContinuousSpaceCount;
87 yyIndentationSize > 0 ? yyContinuousSpaceCount / yyIndentationSize : 0;
88 while (!yyContextStack.isEmpty() && yyContextStack.top().second >= indent)
91 yyContextStack.push({ yyIdent, indent });
95 if (yyIndentationSize < 0 && yyContinuousSpaceCount > 0)
96 yyIndentationSize = yyContinuousSpaceCount;
97 if (!yyContextStack.isEmpty()) {
100 const int classIndent = yyIndentationSize > 0
101 ? yyContinuousSpaceCount / yyIndentationSize - 1
103 while (!yyContextStack.isEmpty() && yyContextStack.top().second > classIndent)
104 yyContextStack.pop();
111 const int lineNo = yyCurLineNo;
112 if (match(Tok_LeftParen) && matchString(&text)) {
116 MetaStrings metaBackup = std::move(metaStrings);
118 if (match(Tok_RightParen)) {
120 }
else if (match(Tok_Comma) && matchStringOrNone(&comment)) {
122 if (match(Tok_RightParen)) {
124 }
else if (match(Tok_Comma)) {
130 if (prefix.isEmpty())
131 context = defaultContext;
132 else if (prefix ==
"self")
133 context = yyContextStack.isEmpty() ? initialContext
134 : yyContextStack.top().first;
139 TranslatorMessage message(QString::fromUtf8(context), QString::fromUtf8(text),
140 QString::fromUtf8(comment), {}, yyFileName, lineNo,
141 {}, TranslatorMessage::Unfinished, plural);
142 setMessageParameters(&message, metaBackup);
143 tor.extend(message, m_cd);
146 case Tok_translate: {
148 const int lineNo = yyCurLineNo;
149 MetaStrings metaBackup = std::move(metaStrings);
150 if (parseTranslate(&text, &context, &comment, &plural)) {
151 TranslatorMessage message(QString::fromUtf8(context), QString::fromUtf8(text),
152 QString::fromUtf8(comment), {}, yyFileName, lineNo,
153 {}, TranslatorMessage::Unfinished, plural);
154 setMessageParameters(&message, metaBackup);
155 tor.extend(message, m_cd);
157 metaStrings = std::move(metaBackup);
161 if (!prefix.isEmpty())
165 if (yyTok != Tok_Dot)
173 if (yyParenDepth != 0) {
174 qWarning(
"%s: Unbalanced parentheses in Python code", qPrintable(yyFileName));
178 ~PythonParser() { std::fclose(yyInFile); }
181 QHash<QByteArray, Token> fillTokens()
183 QHash<QByteArray, Token> tokens = { {
"None", Tok_None }, {
"class", Tok_class },
184 {
"def", Tok_def }, {
"return", Tok_return },
186 {
"__trUtf8", Tok_trUtf8 } };
188 const auto &nameMap = trFunctionAliasManager.nameToTrFunctionMap();
189 for (
auto it = nameMap.cbegin(), end = nameMap.cend(); it != end; ++it) {
190 switch (it.value()) {
191 case TrFunctionAliasManager::Function_tr:
192 case TrFunctionAliasManager::Function_QT_TR_NOOP:
193 tokens.insert(it.key().toUtf8(), Tok_tr);
195 case TrFunctionAliasManager::Function_trUtf8:
196 tokens.insert(it.key().toUtf8(), Tok_trUtf8);
198 case TrFunctionAliasManager::Function_translate:
199 case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP:
201 case TrFunctionAliasManager::Function_findMessage:
202 tokens.insert(it.key().toUtf8(), Tok_translate);
211 QHash<QByteArray, Token> &getTokens()
213 static QHash<QByteArray, Token> tokens = fillTokens();
229 yyCountingIndentation =
true;
230 yyContinuousSpaceCount = 0;
231 }
else if (yyCountingIndentation && (c == 32 || c == 9)) {
232 yyContinuousSpaceCount++;
234 yyCountingIndentation =
false;
241 int c = getc(yyInFile);
246 void startTokenizer(
const QString &fileName)
251 yyFileName = fileName;
256 yyIndentationSize = -1;
257 yyContinuousSpaceCount = 0;
258 yyContextStack.clear();
261 bool parseStringEscape(
int quoteChar, StringType stringType)
263 static const char tab[] =
"abfnrtv";
264 static const char backTab[] =
"\a\b\f\n\r\t\v";
270 if (stringType == StringType::RawString) {
271 if (yyCh != quoteChar)
272 yyString[yyStringLen++] =
'\\';
273 yyString[yyStringLen++] = yyCh;
278 if (yyCh ==
'x' || yyCh ==
'u' || yyCh ==
'U') {
279 qsizetype maxSize = 2;
282 else if (yyCh ==
'U')
290 while (maxSize-- && std::isxdigit(yyCh)) {
298 sscanf_s(hex.constData(),
"%x", &n);
300 std::sscanf(hex.constData(),
"%x", &n);
303 QByteArray hexChar = QString(QChar(n)).toUtf8();
304 if (yyStringLen <
sizeof(yyString) - hexChar.size())
305 for (
char c : std::as_const(hexChar))
306 yyString[yyStringLen++] = c;
310 if (yyCh >=
'0' && yyCh <
'8') {
319 }
while (yyCh >=
'0' && yyCh <
'8' && n < 3);
321 sscanf_s(oct.constData(),
"%o", &n);
323 std::sscanf(oct.constData(),
"%o", &n);
325 if (yyStringLen <
sizeof(yyString) - 1)
326 yyString[yyStringLen++] =
char(n);
330 const char *p = std::strchr(tab, yyCh);
331 if (yyStringLen <
sizeof(yyString) - 1) {
332 yyString[yyStringLen++] = p ==
nullptr ?
char(yyCh) : backTab[p - tab];
338 Token parseString(StringType stringType = StringType::NoString)
340 int quoteChar = yyCh;
341 bool tripleQuote =
false;
342 bool singleQuote =
true;
347 while (yyCh != EOF) {
348 if (singleQuote && (yyCh ==
'\n' || (in && yyCh == quoteChar)))
351 if (yyCh == quoteChar) {
352 if (peekChar() == quoteChar) {
361 if (yyCh == quoteChar) {
366 }
else if (tripleQuote) {
367 if (yyStringLen <
sizeof(yyString) - 1)
368 yyString[yyStringLen++] =
char(yyCh);
379 if (!parseStringEscape(quoteChar, stringType))
382 char *yStart = yyString + yyStringLen;
384 while (yyCh != EOF && (tripleQuote || yyCh !=
'\n') && yyCh != quoteChar
389 yyStringLen += yp - yStart;
392 yyString[yyStringLen] =
'\0';
394 if (yyCh != quoteChar) {
395 printf(
"%c\n", yyCh);
397 qWarning(
"%s:%d: Unterminated string", qPrintable(yyFileName), yyLineNo);
406 QByteArray readLine()
411 if (yyCh == EOF || yyCh ==
'\n')
413 result.append(
char(yyCh));
418 Token getToken(StringType stringType = StringType::NoString)
422 while (yyCh != EOF) {
423 yyLineNo = yyCurLineNo;
425 if (std::isalpha(yyCh) || yyCh ==
'_') {
427 yyIdent.append(
char(yyCh));
429 }
while (std::isalnum(yyCh) || yyCh ==
'_');
431 return getTokens().value(yyIdent, Tok_Ident);
435 auto comment = QString::fromUtf8(readLine());
436 if (!metaStrings.parse(comment)) {
437 qWarning() << qPrintable(yyFileName) <<
':' << yyLineNo <<
": "
438 << metaStrings.popError().toStdString();
441 if (metaStrings.magicComment()) {
442 auto [context, comment] = *metaStrings.magicComment();
443 TranslatorMessage msg(transcode(context), QString(), transcode(comment),
444 QString(), yyFileName, yyCurLineNo, QStringList(),
445 TranslatorMessage::Finished,
false);
446 msg.setExtraComment(transcode(metaStrings.extracomment().simplified()));
448 tor.setExtras(metaStrings.extra());
455 return parseString(stringType);
459 return Tok_LeftParen;
463 return Tok_RightParen;
483 const bool hex = yyCh ==
'x';
488 while ((hex ? std::isxdigit(yyCh) : std::isdigit(yyCh))) {
493 auto v = ba.toLongLong(&ok);
508 const bool matches = (yyTok == t);
514 bool matchStringStart()
516 if (yyTok == Tok_String)
519 if (yyTok == Tok_Ident && yyIdent.size() == 1) {
520 switch (yyIdent.at(0)) {
522 yyTok = getToken(StringType::RawString);
523 return yyTok == Tok_String;
525 yyTok = getToken(StringType::FormatString);
526 return yyTok == Tok_String;
532 bool matchString(QByteArray *s)
536 while (matchStringStart()) {
544 bool matchStringOrNone(QByteArray *s)
546 bool matches = matchString(s);
549 matches = match(Tok_None);
555
556
557
558
559
560
561
562
563
564
565
566 bool skipExpression()
568 if (match(Tok_Integer))
572 while (parenlevel >= 0) {
574 if (yyTok == Tok_RightParen)
576 else if (yyTok == Tok_LeftParen)
578 else if (yyTok == Tok_Eof)
584 bool parseTranslate(QByteArray *text, QByteArray *context, QByteArray *comment,
bool *plural)
592 if (!match(Tok_LeftParen) || !matchString(context) || !match(Tok_Comma)
593 || !matchString(text)) {
597 if (match(Tok_RightParen))
601 if (!match(Tok_Comma))
605 if (match(Tok_RightParen))
609 if (!matchStringOrNone(comment))
612 if (match(Tok_RightParen))
616 if (!match(Tok_Comma))
620 if (match(Tok_RightParen))
624 if (!skipExpression())
633 if (match(Tok_RightParen))
639 void setMessageParameters(TranslatorMessage *message,
const MetaStrings &meta)
645 message->setExtraComment(transcode(meta.extracomment().simplified()));
646 message->setId(meta.msgid());
647 message->setExtras(meta.extra());
648 if (!meta.label().isEmpty())
649 m_cd.appendError(
"%1:%2: labels cannot be used with text-based translation. "
650 "Ignoring\n"_L1.arg(yyFileName)
658 char yyString[65536];
659 size_t yyStringLen{};
668 int yyIndentationSize{};
669 int yyContinuousSpaceCount{};
670 bool yyCountingIndentation =
false;
672 using ContextPair = QPair<QByteArray,
int>;
674 using ContextStack = QStack<ContextPair>;
675 ContextStack yyContextStack;
676 MetaStrings metaStrings;
678 ConversionData &m_cd;