Revision control

++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
++
++ #include <translator.h>
++
++ #include <QtCore/QDebug>
++ #include <QtCore/QFile>
++ #include <QtCore/QStack>
++ #include <QtCore/QStack>
++ #include <QtCore/QString>
++ #include <QtCore/QCoreApplication>
++ #include <QtCore/QStringConverter>
++ #include <QtCore/QTextStream>
++
++ #include <iostream>
++
++ #include <ctype.h>
++
++ QT_BEGIN_NAMESPACE
++
++ using namespace Qt::Literals::StringLiterals;
++
++ enum { Tok_Eof, Tok_class, Tok_return, Tok_tr,
++ Tok_translate, Tok_Ident, Tok_Package,
++ Tok_Comment, Tok_String, Tok_Colon, Tok_Dot,
++ Tok_LeftBrace, Tok_RightBrace, Tok_LeftParen,
++ Tok_RightParen, Tok_Comma, Tok_Semicolon,
++ Tok_Integer, Tok_Plus, Tok_PlusPlus, Tok_PlusEq, Tok_null };
++
++ class Scope
++ {
++ public:
++ QString name;
++ enum Type {Clazz, Function, Other} type;
++ int line;
++
++ Scope(const QString & name, Type type, int line) :
++ name(name),
++ type(type),
++ line(line)
++ {}
++
++ ~Scope()
++ {}
++ };
++
++ /*
++ The tokenizer maintains the following global variables. The names
++ should be self-explanatory.
++ */
++
++ static QString yyFileName;
++ static QChar yyCh;
++ static QString yyIdent;
++ static QString yyComment;
++ static QString yyString;
++ static bool yyEOF = false;
++
++ static qlonglong yyInteger;
++ static int yyParenDepth;
++ static int yyLineNo;
++ static int yyCurLineNo;
++ static int yyParenLineNo;
++ static int yyTok;
++
++ // the string to read from and current position in the string
++ static QString yyInStr;
++ static int yyInPos;
++
++ // The parser maintains the following global variables.
++ static QString yyPackage;
++ static QStack<Scope*> yyScope;
++
++ std::ostream &yyMsg(int line = 0)
++ {
++ return std::cerr << qPrintable(yyFileName) << ':' << (line ? line : yyLineNo) << ": ";
++ }
++
++ static QChar getChar()
++ {
++ if (yyInPos >= yyInStr.size()) {
++ yyEOF = true;
++ return QChar();
++ }
++ QChar c = yyInStr[yyInPos++];
++ if (c == u'\n')
++ ++yyCurLineNo;
++ return c;
++ }
++
++ static int getToken()
++ {
++ const char tab[] = "bfnrt\"\'\\";
++ const char backTab[] = "\b\f\n\r\t\"\'\\";
++
++ yyIdent.clear();
++ yyComment.clear();
++ yyString.clear();
++
++ while (!yyEOF) {
++ yyLineNo = yyCurLineNo;
++
++ if ( yyCh.isLetter() || yyCh.toLatin1() == '_' ) {
++ do {
++ yyIdent.append(yyCh);
++ yyCh = getChar();
++ } while ( yyCh.isLetterOrNumber() || yyCh.toLatin1() == '_' );
++
++ if (yyTok != Tok_Dot) {
++ switch ( yyIdent.at(0).toLatin1() ) {
++ case 'r':
++ if (yyIdent == "return"_L1)
++ return Tok_return;
++ break;
++ case 'c':
++ if (yyIdent == "class"_L1)
++ return Tok_class;
++ break;
++ case 'n':
++ if (yyIdent == "null"_L1)
++ return Tok_null;
++ break;
++ }
++ }
++ switch ( yyIdent.at(0).toLatin1() ) {
++ case 'p':
++ if (yyIdent == "package"_L1)
++ return Tok_Package;
++ break;
++ case 't':
++ if (yyIdent == "tr"_L1)
++ return Tok_tr;
++ if (yyIdent == "translate"_L1)
++ return Tok_translate;
++ }
++ return Tok_Ident;
++ } else {
++ switch ( yyCh.toLatin1() ) {
++
++ case '/':
++ yyCh = getChar();
++ if (yyCh == u'/') {
++ do {
++ yyCh = getChar();
++ if (yyEOF)
++ break;
++ yyComment.append(yyCh);
++ } while (yyCh != u'\n');
++ return Tok_Comment;
++
++ } else if (yyCh == u'*') {
++ bool metAster = false;
++ bool metAsterSlash = false;
++
++ while ( !metAsterSlash ) {
++ yyCh = getChar();
++ if (yyEOF) {
++ yyMsg() << "Unterminated Java comment.\n";
++ return Tok_Comment;
++ }
++
++ yyComment.append( yyCh );
++
++ if (yyCh == u'*')
++ metAster = true;
++ else if (metAster && yyCh == u'/')
++ metAsterSlash = true;
++ else
++ metAster = false;
++ }
++ yyComment.chop(2);
++ yyCh = getChar();
++
++ return Tok_Comment;
++ }
++ break;
++ case '"':
++ yyCh = getChar();
++
++ while (!yyEOF && yyCh != u'\n' && yyCh != u'"') {
++
++ if (yyCh == u'\\') {
++ yyCh = getChar();
++ if (yyCh == u'u') {
++ yyCh = getChar();
++ uint unicode(0);
++ for (int i = 4; i > 0; --i) {
++ unicode = unicode << 4;
++ if( yyCh.isDigit() ) {
++ unicode += yyCh.digitValue();
++ }
++ else {
++ int sub(yyCh.toLower().toLatin1() - 87);
++ if( sub > 15 || sub < 10) {
++ yyMsg() << "Invalid Unicode value.\n";
++ break;
++ }
++ unicode += sub;
++ }
++ yyCh = getChar();
++ }
++ yyString.append(QChar(unicode));
++ } else if (yyCh == u'\n') {
++ yyCh = getChar();
++ } else if (const char *p = strchr(tab, yyCh.toLatin1()); p) {
++ yyString.append(QLatin1Char(backTab[p - tab]));
++ yyCh = getChar();
++ } else {
++ yyMsg() << "Invalid escaped character \'\\" << qPrintable(yyCh)
++ << "\'\n";
++ yyCh = getChar();
++ }
++ } else {
++ yyString.append(yyCh);
++ yyCh = getChar();
++ }
++ }
++
++ if (yyCh != u'"')
++ yyMsg() << "Unterminated string.\n";
++
++ yyCh = getChar();
++
++ return Tok_String;
++
++ case ':':
++ yyCh = getChar();
++ return Tok_Colon;
++ case '\'':
++ yyCh = getChar();
++
++ if (yyCh == u'\\')
++ yyCh = getChar();
++ do {
++ yyCh = getChar();
++ } while (!yyEOF && yyCh != u'\'');
++ yyCh = getChar();
++ break;
++ case '{':
++ yyCh = getChar();
++ return Tok_LeftBrace;
++ case '}':
++ yyCh = getChar();
++ return Tok_RightBrace;
++ case '(':
++ if (yyParenDepth == 0)
++ yyParenLineNo = yyCurLineNo;
++ yyParenDepth++;
++ yyCh = getChar();
++ return Tok_LeftParen;
++ case ')':
++ if (yyParenDepth == 0)
++ yyParenLineNo = yyCurLineNo;
++ yyParenDepth--;
++ yyCh = getChar();
++ return Tok_RightParen;
++ case ',':
++ yyCh = getChar();
++ return Tok_Comma;
++ case '.':
++ yyCh = getChar();
++ return Tok_Dot;
++ case ';':
++ yyCh = getChar();
++ return Tok_Semicolon;
++ case '+':
++ yyCh = getChar();
++ if (yyCh == u'+') {
++ yyCh = getChar();
++ return Tok_PlusPlus;
++ }
++ if (yyCh == u'=') {
++ yyCh = getChar();
++ return Tok_PlusEq;
++ }
++ return Tok_Plus;
++ case '0':
++ case '1':
++ case '2':
++ case '3':
++ case '4':
++ case '5':
++ case '6':
++ case '7':
++ case '8':
++ case '9':
++ {
++ QByteArray ba;
++ ba += yyCh.toLatin1();
++ yyCh = getChar();
++ bool hex = yyCh == u'x';
++ if ( hex ) {
++ ba += yyCh.toLatin1();
++ yyCh = getChar();
++ }
++ while ( hex ? isxdigit(yyCh.toLatin1()) : yyCh.isDigit() ) {
++ ba += yyCh.toLatin1();
++ yyCh = getChar();
++ }
++ bool ok;
++ yyInteger = ba.toLongLong(&ok);
++ if (ok) return Tok_Integer;
++ break;
++ }
++ default:
++ yyCh = getChar();
++ }
++ }
++ }
++ return Tok_Eof;
++ }
++
++ static bool match( int t )
++ {
++ bool matches = ( yyTok == t );
++ if ( matches )
++ yyTok = getToken();
++ return matches;
++ }
++
++ static bool matchString( QString &s )
++ {
++ if ( yyTok != Tok_String )
++ return false;
++
++ s = yyString;
++ yyTok = getToken();
++ while ( yyTok == Tok_Plus ) {
++ yyTok = getToken();
++ if (yyTok == Tok_String)
++ s += yyString;
++ else {
++ yyMsg() <<
++ "String used in translation can contain only literals"
++ " concatenated with other literals, not expressions or numbers.\n";
++ return false;
++ }
++ yyTok = getToken();
++ }
++ return true;
++ }
++
++ static bool matchStringOrNull(QString &s)
++ {
++ bool matches = matchString(s);
++ if (!matches) {
++ matches = (yyTok == Tok_null);
++ if (matches)
++ yyTok = getToken();
++ }
++ return matches;
++ }
++
++ /*
++ * match any expression that can return a number, which can be
++ * 1. Literal number (e.g. '11')
++ * 2. simple identifier (e.g. 'm_count')
++ * 3. simple function call (e.g. 'size()' )
++ * 4. function call on an object (e.g. 'list.size()')
++ * 5. function call on an object (e.g. 'list->size()')
++ *
++ * Other cases:
++ * size(2,4)
++ * list().size()
++ * list(a,b).size(2,4)
++ * etc...
++ */
++ static bool matchExpression()
++ {
++ if (match(Tok_Integer)) {
++ return true;
++ }
++
++ int parenlevel = 0;
++ while (match(Tok_Ident) || parenlevel > 0) {
++ if (yyTok == Tok_RightParen) {
++ if (parenlevel == 0) break;
++ --parenlevel;
++ yyTok = getToken();
++ } else if (yyTok == Tok_LeftParen) {
++ yyTok = getToken();
++ if (yyTok == Tok_RightParen) {
++ yyTok = getToken();
++ } else {
++ ++parenlevel;
++ }
++ } else if (yyTok == Tok_Ident) {
++ continue;
++ } else if (parenlevel == 0) {
++ return false;
++ }
++ }
++ return true;
++ }
++
++ static const QString context()
++ {
++ QString context(yyPackage);
++ bool innerClass = false;
++ for (int i = 0; i < yyScope.size(); ++i) {
++ if (yyScope.at(i)->type == Scope::Clazz) {
++ if (innerClass)
++ context.append("$"_L1);
++ else
++ context.append("."_L1);
++
++ context.append(yyScope.at(i)->name);
++ innerClass = true;
++ }
++ }
++ return context;
++ }
++
++ static void recordMessage(
++ Translator *tor, const QString &context, const QString &text, const QString &comment,
++ const QString &extracomment, bool plural, ConversionData &cd)
++ {
++ TranslatorMessage msg(
++ context, text, comment, QString(),
++ yyFileName, yyLineNo, QStringList(),
++ TranslatorMessage::Unfinished, plural);
++ msg.setExtraComment(extracomment.simplified());
++ tor->extend(msg, cd);
++ }
++
++ static void parse(Translator *tor, ConversionData &cd)
++ {
++ QString text;
++ QString com;
++ QString extracomment;
++
++ yyEOF = false;
++ yyCh = getChar();
++
++ yyTok = getToken();
++ while ( yyTok != Tok_Eof ) {
++ switch ( yyTok ) {
++ case Tok_class:
++ yyTok = getToken();
++ if(yyTok == Tok_Ident) {
++ yyScope.push(new Scope(yyIdent, Scope::Clazz, yyLineNo));
++ }
++ else {
++ yyMsg() << "'class' must be followed by a class name.\n";
++ break;
++ }
++ while (!match(Tok_LeftBrace)) {
++ yyTok = getToken();
++ }
++ break;
++
++ case Tok_tr:
++ yyTok = getToken();
++ if ( match(Tok_LeftParen) && matchString(text) ) {
++ com.clear();
++ bool plural = false;
++
++ if ( match(Tok_RightParen) ) {
++ // no comment
++ } else if (match(Tok_Comma) && matchStringOrNull(com)) { //comment
++ if ( match(Tok_RightParen)) {
++ // ok,
++ } else if (match(Tok_Comma)) {
++ plural = true;
++ }
++ }
++ recordMessage(tor, context(), text, com, extracomment, plural, cd);
++ }
++ break;
++ case Tok_translate:
++ {
++ QString contextOverride;
++ yyTok = getToken();
++ if ( match(Tok_LeftParen) &&
++ matchString(contextOverride) &&
++ match(Tok_Comma) &&
++ matchString(text) ) {
++
++ com.clear();
++ bool plural = false;
++ if (!match(Tok_RightParen)) {
++ // look for comment
++ if ( match(Tok_Comma) && matchStringOrNull(com)) {
++ if (!match(Tok_RightParen)) {
++ if (match(Tok_Comma) && matchExpression() && match(Tok_RightParen)) {
++ plural = true;
++ } else {
++ break;
++ }
++ }
++ } else {
++ break;
++ }
++ }
++ recordMessage(tor, contextOverride, text, com, extracomment, plural, cd);
++ }
++ }
++ break;
++
++ case Tok_Ident:
++ yyTok = getToken();
++ break;
++
++ case Tok_Comment:
++ if (yyComment.startsWith(u':')) {
++ yyComment.remove(0, 1);
++ extracomment.append(yyComment);
++ }
++ yyTok = getToken();
++ break;
++
++ case Tok_RightBrace:
++ if ( yyScope.isEmpty() ) {
++ yyMsg() << "Excess closing brace.\n";
++ }
++ else
++ delete (yyScope.pop());
++ extracomment.clear();
++ yyTok = getToken();
++ break;
++
++ case Tok_LeftBrace:
++ yyScope.push(new Scope(QString(), Scope::Other, yyLineNo));
++ yyTok = getToken();
++ break;
++
++ case Tok_Semicolon:
++ extracomment.clear();
++ yyTok = getToken();
++ break;
++
++ case Tok_Package:
++ yyTok = getToken();
++ while(!match(Tok_Semicolon)) {
++ switch(yyTok) {
++ case Tok_Ident:
++ yyPackage.append(yyIdent);
++ break;
++ case Tok_Dot:
++ yyPackage.append("."_L1);
++ break;
++ default:
++ yyMsg() << "'package' must be followed by package name.\n";
++ break;
++ }
++ yyTok = getToken();
++ }
++ break;
++
++ default:
++ yyTok = getToken();
++ }
++ }
++
++ if ( !yyScope.isEmpty() )
++ yyMsg(yyScope.top()->line) << "Unbalanced opening brace.\n";
++ else if ( yyParenDepth != 0 )
++ yyMsg(yyParenLineNo) << "Unbalanced opening parenthesis.\n";
++ }
++
++
++ bool loadJava(Translator &translator, const QString &filename, ConversionData &cd)
++ {
++ QFile file(filename);
++ if (!file.open(QIODevice::ReadOnly)) {
++ cd.appendError(QStringLiteral("Cannot open %1: %2").arg(filename, file.errorString()));
++ return false;
++ }
++
++ yyInPos = -1;
++ yyFileName = filename;
++ yyPackage.clear();
++ yyScope.clear();
++ yyTok = -1;
++ yyParenDepth = 0;
++ yyCurLineNo = 0;
++ yyParenLineNo = 1;
++
++ QTextStream ts(&file);
++ ts.setEncoding(cd.m_sourceIsUtf16 ? QStringConverter::Utf16 : QStringConverter::Utf8);
++ ts.setAutoDetectUnicode(true);
++ yyInStr = ts.readAll();
++ yyInPos = 0;
++ yyFileName = filename;
++ yyCurLineNo = 1;
++ yyParenLineNo = 1;
++
++ parse(&translator, cd);
++ return true;
++ }
++
++ QT_END_NAMESPACE