source: trunk/tools/linguist/shared/java.cpp@ 275

Last change on this file since 275 was 2, checked in by Dmitry A. Kuminov, 16 years ago

Initially imported qt-all-opensource-src-4.5.1 from Trolltech.

File size: 20.2 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4** Contact: Qt Software Information ([email protected])
5**
6** This file is part of the Qt Linguist of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial Usage
10** Licensees holding valid Qt Commercial licenses may use this file in
11** accordance with the Qt Commercial License Agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and Nokia.
14**
15** GNU Lesser General Public License Usage
16** Alternatively, this file may be used under the terms of the GNU Lesser
17** General Public License version 2.1 as published by the Free Software
18** Foundation and appearing in the file LICENSE.LGPL included in the
19** packaging of this file. Please review the following information to
20** ensure the GNU Lesser General Public License version 2.1 requirements
21** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
22**
23** In addition, as a special exception, Nokia gives you certain
24** additional rights. These rights are described in the Nokia Qt LGPL
25** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
26** package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you are unsure which license is appropriate for your use, please
37** contact the sales department at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "translator.h"
43
44#include <QtCore/QDebug>
45#include <QtCore/QFile>
46#include <QtCore/QRegExp>
47#include <QtCore/QStack>
48#include <QtCore/QStack>
49#include <QtCore/QString>
50#include <QtCore/QTextCodec>
51
52#include <ctype.h>
53
54QT_BEGIN_NAMESPACE
55
56enum { Tok_Eof, Tok_class, Tok_return, Tok_tr,
57 Tok_translate, Tok_Ident, Tok_Package,
58 Tok_Comment, Tok_String, Tok_Colon, Tok_Dot,
59 Tok_LeftBrace, Tok_RightBrace, Tok_LeftParen,
60 Tok_RightParen, Tok_Comma, Tok_Semicolon,
61 Tok_Integer, Tok_Plus, Tok_PlusPlus, Tok_PlusEq };
62
63class Scope
64{
65 public:
66 QString name;
67 enum Type {Clazz, Function, Other} type;
68 int line;
69
70 Scope(const QString & name, Type type, int line) :
71 name(name),
72 type(type),
73 line(line)
74 {}
75
76 ~Scope()
77 {}
78};
79
80/*
81 The tokenizer maintains the following global variables. The names
82 should be self-explanatory.
83*/
84
85static QString yyFileName;
86static QChar yyCh;
87static QString yyIdent;
88static QString yyComment;
89static QString yyString;
90
91
92static qlonglong yyInteger;
93static int yyParenDepth;
94static int yyLineNo;
95static int yyCurLineNo;
96static int yyParenLineNo;
97static int yyTok;
98
99// the string to read from and current position in the string
100static QString yyInStr;
101static int yyInPos;
102
103// The parser maintains the following global variables.
104static QString yyPackage;
105static QStack<Scope*> yyScope;
106static QString yyDefaultContext;
107
108static QChar getChar()
109{
110 if (yyInPos >= yyInStr.size())
111 return EOF;
112 QChar c = yyInStr[yyInPos++];
113 if (c.unicode() == '\n')
114 ++yyCurLineNo;
115 return c.unicode();
116}
117
118static int getToken()
119{
120 const char tab[] = "bfnrt\"\'\\";
121 const char backTab[] = "\b\f\n\r\t\"\'\\";
122
123 yyIdent.clear();
124 yyComment.clear();
125 yyString.clear();
126
127 while ( yyCh != EOF ) {
128 yyLineNo = yyCurLineNo;
129
130 if ( yyCh.isLetter() || yyCh.toLatin1() == '_' ) {
131 do {
132 yyIdent.append(yyCh);
133 yyCh = getChar();
134 } while ( yyCh.isLetterOrNumber() || yyCh.toLatin1() == '_' );
135
136 if (yyTok != Tok_Dot) {
137 switch ( yyIdent.at(0).toLatin1() ) {
138 case 'r':
139 if ( yyIdent == QLatin1String("return") )
140 return Tok_return;
141 break;
142 case 'c':
143 if ( yyIdent == QLatin1String("class") )
144 return Tok_class;
145 break;
146 }
147 }
148 switch ( yyIdent.at(0).toLatin1() ) {
149 case 'T':
150 // TR() for when all else fails
151 if ( yyIdent == QLatin1String("TR") )
152 return Tok_tr;
153 break;
154 case 'p':
155 if( yyIdent == QLatin1String("package") )
156 return Tok_Package;
157 break;
158 case 't':
159 if ( yyIdent == QLatin1String("tr") )
160 return Tok_tr;
161 if ( yyIdent == QLatin1String("translate") )
162 return Tok_translate;
163 }
164 return Tok_Ident;
165 } else {
166 switch ( yyCh.toLatin1() ) {
167
168 case '/':
169 yyCh = getChar();
170 if ( yyCh == QLatin1Char('/') ) {
171 do {
172 yyCh = getChar();
173 if (yyCh == EOF)
174 break;
175 yyComment.append(yyCh);
176 } while (yyCh != QLatin1Char('\n'));
177 return Tok_Comment;
178
179 } else if ( yyCh == QLatin1Char('*') ) {
180 bool metAster = false;
181 bool metAsterSlash = false;
182
183 while ( !metAsterSlash ) {
184 yyCh = getChar();
185 if ( yyCh == EOF ) {
186 qFatal( "%s: Unterminated Java comment starting at"
187 " line %d\n",
188 qPrintable(yyFileName), yyLineNo );
189
190 return Tok_Comment;
191 }
192
193 yyComment.append( yyCh );
194
195 if ( yyCh == QLatin1Char('*') )
196 metAster = true;
197 else if ( metAster && yyCh == QLatin1Char('/') )
198 metAsterSlash = true;
199 else
200 metAster = false;
201 }
202 yyComment.chop(2);
203 yyCh = getChar();
204
205 return Tok_Comment;
206 }
207 break;
208 case '"':
209 yyCh = getChar();
210
211 while ( yyCh != EOF && yyCh != QLatin1Char('\n') && yyCh != QLatin1Char('"') ) {
212 if ( yyCh == QLatin1Char('\\') ) {
213 yyCh = getChar();
214 if ( yyCh == QLatin1Char('u') ) {
215 yyCh = getChar();
216 uint unicode(0);
217 for (int i = 4; i > 0; --i) {
218 unicode = unicode << 4;
219 if( yyCh.isDigit() ) {
220 unicode += yyCh.digitValue();
221 }
222 else {
223 int sub(yyCh.toLower().toAscii() - 87);
224 if( sub > 15 || sub < 10) {
225 qFatal( "%s:%d: Invalid Unicode",
226 qPrintable(yyFileName), yyLineNo );
227 }
228 unicode += sub;
229 }
230 yyCh = getChar();
231 }
232 yyString.append(QChar(unicode));
233 }
234 else if ( yyCh == QLatin1Char('\n') ) {
235 yyCh = getChar();
236 }
237 else {
238 yyString.append( QLatin1Char(backTab[strchr( tab, yyCh.toAscii() ) - tab]) );
239 yyCh = getChar();
240 }
241 } else {
242 yyString.append(yyCh);
243 yyCh = getChar();
244 }
245 }
246
247 if ( yyCh != QLatin1Char('"') )
248 qFatal( "%s:%d: Unterminated string",
249 qPrintable(yyFileName), yyLineNo );
250
251 yyCh = getChar();
252
253 return Tok_String;
254
255 case ':':
256 yyCh = getChar();
257 return Tok_Colon;
258 case '\'':
259 yyCh = getChar();
260
261 if ( yyCh == QLatin1Char('\\') )
262 yyCh = getChar();
263 do {
264 yyCh = getChar();
265 } while ( yyCh != EOF && yyCh != QLatin1Char('\'') );
266 yyCh = getChar();
267 break;
268 case '{':
269 yyCh = getChar();
270 return Tok_LeftBrace;
271 case '}':
272 yyCh = getChar();
273 return Tok_RightBrace;
274 case '(':
275 if (yyParenDepth == 0)
276 yyParenLineNo = yyCurLineNo;
277 yyParenDepth++;
278 yyCh = getChar();
279 return Tok_LeftParen;
280 case ')':
281 if (yyParenDepth == 0)
282 yyParenLineNo = yyCurLineNo;
283 yyParenDepth--;
284 yyCh = getChar();
285 return Tok_RightParen;
286 case ',':
287 yyCh = getChar();
288 return Tok_Comma;
289 case '.':
290 yyCh = getChar();
291 return Tok_Dot;
292 case ';':
293 yyCh = getChar();
294 return Tok_Semicolon;
295 case '+':
296 yyCh = getChar();
297 if (yyCh == QLatin1Char('+')) {
298 yyCh = getChar();
299 return Tok_PlusPlus;
300 }
301 if( yyCh == QLatin1Char('=') ){
302 yyCh = getChar();
303 return Tok_PlusEq;
304 }
305 return Tok_Plus;
306 case '0':
307 case '1':
308 case '2':
309 case '3':
310 case '4':
311 case '5':
312 case '6':
313 case '7':
314 case '8':
315 case '9':
316 {
317 QByteArray ba;
318 ba += yyCh.toLatin1();
319 yyCh = getChar();
320 bool hex = yyCh == QLatin1Char('x');
321 if ( hex ) {
322 ba += yyCh.toLatin1();
323 yyCh = getChar();
324 }
325 while ( hex ? isxdigit(yyCh.toLatin1()) : yyCh.isDigit() ) {
326 ba += yyCh.toLatin1();
327 yyCh = getChar();
328 }
329 bool ok;
330 yyInteger = ba.toLongLong(&ok);
331 if (ok) return Tok_Integer;
332 break;
333 }
334 default:
335 yyCh = getChar();
336 }
337 }
338 }
339 return Tok_Eof;
340}
341
342static bool match( int t )
343{
344 bool matches = ( yyTok == t );
345 if ( matches )
346 yyTok = getToken();
347 return matches;
348}
349
350static bool matchString( QString &s )
351{
352 if ( yyTok != Tok_String )
353 return false;
354
355 s = yyString;
356 yyTok = getToken();
357 while ( yyTok == Tok_Plus ) {
358 yyTok = getToken();
359 if (yyTok == Tok_String)
360 s += yyString;
361 else {
362 qWarning( "%s:%d: String used in translation can only contain strings"
363 " concatenated with other strings, not expressions or numbers.",
364 qPrintable(yyFileName), yyLineNo );
365 return false;
366 }
367 yyTok = getToken();
368 }
369 return true;
370}
371
372static bool matchInteger( qlonglong *number)
373{
374 bool matches = (yyTok == Tok_Integer);
375 if (matches) {
376 yyTok = getToken();
377 *number = yyInteger;
378 }
379 return matches;
380}
381
382static bool matchStringOrNull(QString &s)
383{
384 bool matches = matchString(s);
385 qlonglong num = 0;
386 if (!matches) matches = matchInteger(&num);
387 return matches && num == 0;
388}
389
390/*
391 * match any expression that can return a number, which can be
392 * 1. Literal number (e.g. '11')
393 * 2. simple identifier (e.g. 'm_count')
394 * 3. simple function call (e.g. 'size()' )
395 * 4. function call on an object (e.g. 'list.size()')
396 * 5. function call on an object (e.g. 'list->size()')
397 *
398 * Other cases:
399 * size(2,4)
400 * list().size()
401 * list(a,b).size(2,4)
402 * etc...
403 */
404static bool matchExpression()
405{
406 if (match(Tok_Integer)) {
407 return true;
408 }
409
410 int parenlevel = 0;
411 while (match(Tok_Ident) || parenlevel > 0) {
412 if (yyTok == Tok_RightParen) {
413 if (parenlevel == 0) break;
414 --parenlevel;
415 yyTok = getToken();
416 } else if (yyTok == Tok_LeftParen) {
417 yyTok = getToken();
418 if (yyTok == Tok_RightParen) {
419 yyTok = getToken();
420 } else {
421 ++parenlevel;
422 }
423 } else if (yyTok == Tok_Ident) {
424 continue;
425 } else if (parenlevel == 0) {
426 return false;
427 }
428 }
429 return true;
430}
431
432static const QString context()
433{
434 QString context(yyPackage);
435 bool innerClass = false;
436 for (int i = 0; i < yyScope.size(); ++i) {
437 if (yyScope.at(i)->type == Scope::Clazz) {
438 if (innerClass)
439 context.append(QLatin1String("$"));
440 else
441 context.append(QLatin1String("."));
442
443 context.append(yyScope.at(i)->name);
444 innerClass = true;
445 }
446 }
447 return context.isEmpty() ? yyDefaultContext : context;
448}
449
450static void recordMessage(
451 Translator *tor, const QString &context, const QString &text, const QString &comment,
452 const QString &extracomment, bool plural)
453{
454 TranslatorMessage msg(
455 context, text, comment, QString(),
456 yyFileName, yyLineNo, QStringList(),
457 TranslatorMessage::Unfinished, plural);
458 msg.setExtraComment(extracomment.simplified());
459 tor->extend(msg);
460}
461
462static void parse( Translator *tor )
463{
464 QString text;
465 QString com;
466 QString extracomment;
467
468 yyCh = getChar();
469
470 yyTok = getToken();
471 while ( yyTok != Tok_Eof ) {
472 switch ( yyTok ) {
473 case Tok_class:
474 yyTok = getToken();
475 if(yyTok == Tok_Ident) {
476 yyScope.push(new Scope(yyIdent, Scope::Clazz, yyLineNo));
477 }
478 else {
479 qFatal( "%s:%d: Class must be followed by a classname",
480 qPrintable(yyFileName), yyLineNo );
481 }
482 while (!match(Tok_LeftBrace)) {
483 yyTok = getToken();
484 }
485 break;
486
487 case Tok_tr:
488 yyTok = getToken();
489 if ( match(Tok_LeftParen) && matchString(text) ) {
490 com.clear();
491 bool plural = false;
492
493 if ( match(Tok_RightParen) ) {
494 // no comment
495 } else if (match(Tok_Comma) && matchStringOrNull(com)) { //comment
496 if ( match(Tok_RightParen)) {
497 // ok,
498 } else if (match(Tok_Comma)) {
499 plural = true;
500 }
501 }
502 if (!text.isEmpty())
503 recordMessage(tor, context(), text, com, extracomment, plural);
504 }
505 break;
506 case Tok_translate:
507 {
508 QString contextOverride;
509 yyTok = getToken();
510 if ( match(Tok_LeftParen) &&
511 matchString(contextOverride) &&
512 match(Tok_Comma) &&
513 matchString(text) ) {
514
515 com.clear();
516 bool plural = false;
517 if (!match(Tok_RightParen)) {
518 // look for comment
519 if ( match(Tok_Comma) && matchStringOrNull(com)) {
520 if (!match(Tok_RightParen)) {
521 if (match(Tok_Comma) && matchExpression() && match(Tok_RightParen)) {
522 plural = true;
523 } else {
524 break;
525 }
526 }
527 } else {
528 break;
529 }
530 }
531 if (!text.isEmpty())
532 recordMessage(tor, contextOverride, text, com, extracomment, plural);
533 }
534 }
535 break;
536
537 case Tok_Ident:
538 yyTok = getToken();
539 break;
540
541 case Tok_Comment:
542 if (yyComment.startsWith(QLatin1Char(':'))) {
543 yyComment.remove(0, 1);
544 extracomment.append(yyComment);
545 }
546 yyTok = getToken();
547 break;
548
549 case Tok_RightBrace:
550 if ( yyScope.isEmpty() ) {
551 qFatal( "%s:%d: Unbalanced right brace in Java code\n",
552 qPrintable(yyFileName), yyLineNo );
553 }
554 else
555 delete (yyScope.pop());
556 extracomment.clear();
557 yyTok = getToken();
558 break;
559
560 case Tok_LeftBrace:
561 yyScope.push(new Scope(QString(), Scope::Other, yyLineNo));
562 yyTok = getToken();
563 break;
564
565 case Tok_Semicolon:
566 extracomment.clear();
567 yyTok = getToken();
568 break;
569
570 case Tok_Package:
571 yyTok = getToken();
572 while(!match(Tok_Semicolon)) {
573 switch(yyTok) {
574 case Tok_Ident:
575 yyPackage.append(yyIdent);
576 break;
577 case Tok_Dot:
578 yyPackage.append(QLatin1String("."));
579 break;
580 default:
581 qFatal( "%s:%d: Package keyword should be followed by com.package.name;",
582 qPrintable(yyFileName), yyLineNo );
583 break;
584 }
585 yyTok = getToken();
586 }
587 break;
588
589 default:
590 yyTok = getToken();
591 }
592 }
593
594 if ( !yyScope.isEmpty() )
595 qFatal( "%s:%d: Unbalanced braces in Java code\n",
596 qPrintable(yyFileName), yyScope.top()->line );
597 else if ( yyParenDepth != 0 )
598 qFatal( "%s:%d: Unbalanced parentheses in Java code\n",
599 qPrintable(yyFileName), yyParenLineNo );
600}
601
602
603bool loadJava(Translator &translator, QIODevice &dev, ConversionData &cd)
604{
605 //void LupdateApplication::fetchtr_java( const QString &fileName, Translator *tor,
606 //const QString &defaultContext, bool mustExist, const QByteArray &codecForSource )
607
608 yyDefaultContext = cd.m_defaultContext;
609 yyInPos = -1;
610 yyFileName = cd.m_sourceFileName;
611 yyPackage.clear();
612 yyScope.clear();
613 yyTok = -1;
614 yyParenDepth = 0;
615 yyCurLineNo = 0;
616 yyParenLineNo = 1;
617
618 QTextStream ts(&dev);
619 QByteArray codecName;
620 if (!cd.m_codecForSource.isEmpty())
621 codecName = cd.m_codecForSource;
622 else
623 codecName = translator.codecName(); // Just because it should be latin1 already
624 ts.setCodec(QTextCodec::codecForName(codecName));
625 ts.setAutoDetectUnicode(true);
626 yyInStr = ts.readAll();
627 yyInPos = 0;
628 yyFileName = cd.m_sourceFileName;
629 yyCurLineNo = 1;
630 yyParenLineNo = 1;
631 yyCh = getChar();
632
633 parse(&translator);
634
635 // Java uses UTF-16 internally and Jambi makes UTF-8 for tr() purposes of it.
636 translator.setCodecName("UTF-8");
637 return true;
638}
639
640int initJava()
641{
642 Translator::FileFormat format;
643 format.extension = QLatin1String("java");
644 format.fileType = Translator::FileFormat::SourceCode;
645 format.priority = 0;
646 format.description = QObject::tr("Java source files");
647 format.loader = &loadJava;
648 format.saver = 0;
649 Translator::registerFileFormat(format);
650 return 1;
651}
652
653Q_CONSTRUCTOR_FUNCTION(initJava)
654
655QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.