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 = {})
81 while (yyTok != Tok_Eof) {
85 if (yyIndentationSize < 0 && yyContinuousSpaceCount > 0)
86 yyIndentationSize = yyContinuousSpaceCount;
88 yyIndentationSize > 0 ? yyContinuousSpaceCount / yyIndentationSize : 0;
89 while (!yyContextStack.isEmpty() && yyContextStack.top().second >= indent)
92 yyContextStack.push({ yyIdent, indent });
96 if (yyIndentationSize < 0 && yyContinuousSpaceCount > 0)
97 yyIndentationSize = yyContinuousSpaceCount;
98 if (!yyContextStack.isEmpty()) {
101 const int classIndent = yyIndentationSize > 0
102 ? yyContinuousSpaceCount / yyIndentationSize - 1
104 while (!yyContextStack.isEmpty() && yyContextStack.top().second > classIndent)
105 yyContextStack.pop();
113 const int lineNo = yyCurLineNo;
114 if (match(Tok_LeftParen) && matchString(&text)) {
118 MetaStrings metaBackup = std::move(metaStrings);
120 if (match(Tok_RightParen)) {
122 }
else if (match(Tok_Comma) && matchStringOrNone(&comment)) {
124 if (match(Tok_RightParen)) {
126 }
else if (match(Tok_Comma)) {
132 if (prefix.isEmpty())
133 context = defaultContext;
134 else if (prefix ==
"self")
135 context = yyContextStack.isEmpty() ? initialContext
136 : yyContextStack.top().first;
141 TranslatorMessage message(QString::fromUtf8(context), QString::fromUtf8(text),
142 QString::fromUtf8(comment), {}, yyFileName, lineNo,
143 {}, TranslatorMessage::Unfinished, plural);
144 setMessageParameters(&message, metaBackup);
145 tor.extend(message, m_cd);
148 case Tok_translate: {
150 const int lineNo = yyCurLineNo;
151 MetaStrings metaBackup = std::move(metaStrings);
152 if (parseTranslate(&text, &context, &comment, &utf8, &plural)) {
153 TranslatorMessage message(QString::fromUtf8(context), QString::fromUtf8(text),
154 QString::fromUtf8(comment), {}, yyFileName, lineNo,
155 {}, TranslatorMessage::Unfinished, plural);
156 setMessageParameters(&message, metaBackup);
157 tor.extend(message, m_cd);
159 metaStrings = std::move(metaBackup);
163 if (!prefix.isEmpty())
167 if (yyTok != Tok_Dot)
175 if (yyParenDepth != 0) {
176 qWarning(
"%s: Unbalanced parentheses in Python code", qPrintable(yyFileName));
180 ~PythonParser() { std::fclose(yyInFile); }
183 QHash<QByteArray, Token> fillTokens()
185 QHash<QByteArray, Token> tokens = { {
"None", Tok_None }, {
"class", Tok_class },
186 {
"def", Tok_def }, {
"return", Tok_return },
188 {
"__trUtf8", Tok_trUtf8 } };
190 const auto &nameMap = trFunctionAliasManager.nameToTrFunctionMap();
191 for (
auto it = nameMap.cbegin(), end = nameMap.cend(); it != end; ++it) {
192 switch (it.value()) {
193 case TrFunctionAliasManager::Function_tr:
194 case TrFunctionAliasManager::Function_QT_TR_NOOP:
195 tokens.insert(it.key().toUtf8(), Tok_tr);
197 case TrFunctionAliasManager::Function_trUtf8:
198 tokens.insert(it.key().toUtf8(), Tok_trUtf8);
200 case TrFunctionAliasManager::Function_translate:
201 case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP:
203 case TrFunctionAliasManager::Function_findMessage:
204 tokens.insert(it.key().toUtf8(), Tok_translate);
213 QHash<QByteArray, Token> &getTokens()
215 static QHash<QByteArray, Token> tokens = fillTokens();
231 yyCountingIndentation =
true;
232 yyContinuousSpaceCount = 0;
233 }
else if (yyCountingIndentation && (c == 32 || c == 9)) {
234 yyContinuousSpaceCount++;
236 yyCountingIndentation =
false;
243 int c = getc(yyInFile);
248 void startTokenizer(
const QString &fileName)
253 yyFileName = fileName;
258 yyIndentationSize = -1;
259 yyContinuousSpaceCount = 0;
260 yyContextStack.clear();
263 bool parseStringEscape(
int quoteChar, StringType stringType)
265 static const char tab[] =
"abfnrtv";
266 static const char backTab[] =
"\a\b\f\n\r\t\v";
272 if (stringType == StringType::RawString) {
273 if (yyCh != quoteChar)
274 yyString[yyStringLen++] =
'\\';
275 yyString[yyStringLen++] = yyCh;
280 if (yyCh ==
'x' || yyCh ==
'u' || yyCh ==
'U') {
281 qsizetype maxSize = 2;
284 else if (yyCh ==
'U')
292 while (maxSize-- && std::isxdigit(yyCh)) {
300 sscanf_s(hex,
"%x", &n);
302 std::sscanf(hex,
"%x", &n);
305 QByteArray hexChar = QString(QChar(n)).toUtf8();
306 if (yyStringLen <
sizeof(yyString) - hexChar.size())
307 for (
char c : std::as_const(hexChar))
308 yyString[yyStringLen++] = c;
312 if (yyCh >=
'0' && yyCh <
'8') {
321 }
while (yyCh >=
'0' && yyCh <
'8' && n < 3);
323 sscanf_s(oct,
"%o", &n);
325 std::sscanf(oct,
"%o", &n);
327 if (yyStringLen <
sizeof(yyString) - 1)
328 yyString[yyStringLen++] =
char(n);
332 const char *p = std::strchr(tab, yyCh);
333 if (yyStringLen <
sizeof(yyString) - 1) {
334 yyString[yyStringLen++] = p ==
nullptr ?
char(yyCh) : backTab[p - tab];
340 Token parseString(StringType stringType = StringType::NoString)
342 int quoteChar = yyCh;
343 bool tripleQuote =
false;
344 bool singleQuote =
true;
349 while (yyCh != EOF) {
350 if (singleQuote && (yyCh ==
'\n' || (in && yyCh == quoteChar)))
353 if (yyCh == quoteChar) {
354 if (peekChar() == quoteChar) {
363 if (yyCh == quoteChar) {
368 }
else if (tripleQuote) {
369 if (yyStringLen <
sizeof(yyString) - 1)
370 yyString[yyStringLen++] =
char(yyCh);
381 if (!parseStringEscape(quoteChar, stringType))
384 char *yStart = yyString + yyStringLen;
386 while (yyCh != EOF && (tripleQuote || yyCh !=
'\n') && yyCh != quoteChar
391 yyStringLen += yp - yStart;
394 yyString[yyStringLen] =
'\0';
396 if (yyCh != quoteChar) {
397 printf(
"%c\n", yyCh);
399 qWarning(
"%s:%d: Unterminated string", qPrintable(yyFileName), yyLineNo);
408 QByteArray readLine()
413 if (yyCh == EOF || yyCh ==
'\n')
415 result.append(
char(yyCh));
420 Token getToken(StringType stringType = StringType::NoString)
424 while (yyCh != EOF) {
425 yyLineNo = yyCurLineNo;
427 if (std::isalpha(yyCh) || yyCh ==
'_') {
429 yyIdent.append(
char(yyCh));
431 }
while (std::isalnum(yyCh) || yyCh ==
'_');
433 return getTokens().value(yyIdent, Tok_Ident);
437 auto comment = QString::fromUtf8(readLine());
438 if (!metaStrings.parse(comment)) {
439 qWarning() << qPrintable(yyFileName) <<
':' << yyLineNo <<
": "
440 << metaStrings.popError().toStdString();
443 if (metaStrings.magicComment()) {
444 auto [context, comment] = *metaStrings.magicComment();
445 TranslatorMessage msg(ParserTool::transcode(context), QString(),
446 ParserTool::transcode(comment), QString(), yyFileName,
447 yyCurLineNo, QStringList(), TranslatorMessage::Finished,
450 ParserTool::transcode(metaStrings.extracomment().simplified()));
452 tor.setExtras(metaStrings.extra());
459 return parseString(stringType);
463 return Tok_LeftParen;
467 return Tok_RightParen;
487 const bool hex = yyCh ==
'x';
492 while ((hex ? std::isxdigit(yyCh) : std::isdigit(yyCh))) {
497 auto v = ba.toLongLong(&ok);
512 const bool matches = (yyTok == t);
518 bool matchStringStart()
520 if (yyTok == Tok_String)
523 if (yyTok == Tok_Ident && yyIdent.size() == 1) {
524 switch (yyIdent.at(0)) {
526 yyTok = getToken(StringType::RawString);
527 return yyTok == Tok_String;
529 yyTok = getToken(StringType::FormatString);
530 return yyTok == Tok_String;
536 bool matchString(QByteArray *s)
540 while (matchStringStart()) {
548 bool matchEncoding(
bool *utf8)
551 if (yyTok == Tok_Ident && std::strcmp(yyIdent,
"PySide6") == 0) {
554 if (yyTok != Tok_Dot)
560 if (yyTok == Tok_Ident
561 && (std::strcmp(yyIdent,
"QtGui") == 0 || std::strcmp(yyIdent,
"QtCore") == 0)) {
564 if (yyTok != Tok_Dot)
570 if (yyTok == Tok_Ident) {
571 if (std::strcmp(yyIdent,
"QApplication") == 0
572 || std::strcmp(yyIdent,
"QGuiApplication") == 0
573 || std::strcmp(yyIdent,
"QCoreApplication") == 0) {
576 if (yyTok == Tok_Dot)
580 *utf8 = QByteArray(yyIdent).endsWith(
"UTF8");
587 bool matchStringOrNone(QByteArray *s)
589 bool matches = matchString(s);
592 matches = match(Tok_None);
598
599
600
601
602
603
604
605
606
607
608
609 bool matchExpression()
611 if (match(Tok_Integer))
615 while (match(Tok_Ident) || parenlevel > 0) {
616 if (yyTok == Tok_RightParen) {
621 }
else if (yyTok == Tok_LeftParen) {
623 if (yyTok == Tok_RightParen) {
628 }
else if (yyTok == Tok_Ident) {
630 }
else if (parenlevel == 0) {
637 bool parseTranslate(QByteArray *text, QByteArray *context, QByteArray *comment,
bool *utf8,
647 if (!match(Tok_LeftParen) || !matchString(context) || !match(Tok_Comma)
648 || !matchString(text)) {
652 if (match(Tok_RightParen))
656 if (!match(Tok_Comma))
660 if (match(Tok_RightParen))
664 if (!matchStringOrNone(comment))
667 if (match(Tok_RightParen))
671 if (!match(Tok_Comma))
675 if (match(Tok_RightParen))
679 if (matchEncoding(utf8)) {
680 if (match(Tok_RightParen))
684 if (!match(Tok_Comma))
689 if (match(Tok_RightParen))
694 if (!matchExpression())
703 if (match(Tok_RightParen))
709 void setMessageParameters(TranslatorMessage *message,
const MetaStrings &meta)
715 message->setExtraComment(ParserTool::transcode(meta.extracomment().simplified()));
716 message->setId(meta.msgid());
717 message->setExtras(meta.extra());
718 if (!meta.label().isEmpty() && meta.msgid().isEmpty())
719 m_cd.appendError(
"%1:%2: labels cannot be used with text-based translation. "
720 "Ignoring\n"_L1.arg(yyFileName)
723 message->setLabel(meta.label());
730 char yyString[65536];
731 size_t yyStringLen{};
740 int yyIndentationSize{};
741 int yyContinuousSpaceCount{};
742 bool yyCountingIndentation =
false;
744 using ContextPair = QPair<QByteArray,
int>;
746 using ContextStack = QStack<ContextPair>;
747 ContextStack yyContextStack;
748 MetaStrings metaStrings;
750 ConversionData &m_cd;