1 /*
2     SPDX-License-Identifier: GPL-2.0-or-later
3     SPDX-FileCopyrightText: 2006-2020 Umbrello UML Modeller Authors <umbrello-devel@kde.org>
4 */
5 
6 // own header
7 #include "pascalimport.h"
8 
9 // app includes
10 #include "attribute.h"
11 #include "classifier.h"
12 #include "debug_utils.h"
13 #include "enum.h"
14 #include "import_utils.h"
15 #include "operation.h"
16 #include "package.h"
17 #include "uml.h"
18 #include "umldoc.h"
19 
20 // qt includes
21 #include <QRegExp>
22 
23 #include <stdio.h>
24 
25 /**
26  * Constructor.
27  */
PascalImport(CodeImpThread * thread)28 PascalImport::PascalImport(CodeImpThread* thread) : NativeImportBase(QLatin1String("//"), thread)
29 {
30     setMultiLineComment(QLatin1String("(*"), QLatin1String("*)"));
31     setMultiLineAltComment(QLatin1String("{"), QLatin1String("}"));
32     initVars();
33 }
34 
35 /**
36  * Destructor.
37  */
~PascalImport()38 PascalImport::~PascalImport()
39 {
40 }
41 
42 /**
43  * Reimplement operation from NativeImportBase.
44  */
initVars()45 void PascalImport::initVars()
46 {
47     m_inInterface = false;
48     m_section = sect_NONE;
49     NativeImportBase::m_currentAccess = Uml::Visibility::Public;
50 }
51 
52 /**
53  * Implement abstract operation from NativeImportBase.
54  */
fillSource(const QString & word)55 void PascalImport::fillSource(const QString& word)
56 {
57     QString lexeme;
58     const uint len = word.length();
59     for (uint i = 0; i < len; ++i) {
60         QChar c = word[i];
61         if (c.isLetterOrNumber() || c == QLatin1Char('_') || c == QLatin1Char('.') || c == QLatin1Char('#')) {
62             lexeme += c;
63         } else {
64             if (!lexeme.isEmpty()) {
65                 m_source.append(lexeme);
66                 lexeme.clear();
67             }
68             if (i+1 < len && c == QLatin1Char(':') && word[i + 1] == QLatin1Char('=')) {
69                 m_source.append(QLatin1String(":="));
70                 i++;
71             } else {
72                 m_source.append(QString(c));
73             }
74         }
75     }
76     if (!lexeme.isEmpty())
77         m_source.append(lexeme);
78 }
79 
80 /**
81  * Check for, and skip over, all modifiers following a method.
82  * Set the output arguments on encountering abstract and/or virtual.
83  * @param isVirtual   return value, set to true when "virtual" seen
84  * @param isAbstract  return value, set to true when "abstract" seen
85  */
checkModifiers(bool & isVirtual,bool & isAbstract)86 void PascalImport::checkModifiers(bool& isVirtual, bool& isAbstract)
87 {
88     const int srcLength = m_source.count();
89     while (m_srcIndex < srcLength - 1) {
90         QString lookAhead = m_source[m_srcIndex + 1].toLower();
91         if (lookAhead != QLatin1String("virtual") && lookAhead != QLatin1String("abstract") &&
92             lookAhead != QLatin1String("override") &&
93             lookAhead != QLatin1String("register") && lookAhead != QLatin1String("cdecl") &&
94             lookAhead != QLatin1String("pascal") && lookAhead != QLatin1String("stdcall") &&
95             lookAhead != QLatin1String("safecall") && lookAhead != QLatin1String("saveregisters") &&
96             lookAhead != QLatin1String("popstack"))
97             break;
98         if (lookAhead == QLatin1String("abstract"))
99             isAbstract = true;
100         else if (lookAhead == QLatin1String("virtual"))
101             isVirtual = true;
102         advance();
103         skipStmt();
104     }
105 }
106 
107 /**
108  * Implement abstract operation from NativeImportBase.
109  * @return success status of operation
110  */
parseStmt()111 bool PascalImport::parseStmt()
112 {
113     const int srcLength = m_source.count();
114     QString keyword = m_source[m_srcIndex].toLower();
115     //uDebug() << '"' << keyword << '"';
116     if (keyword == QLatin1String("uses")) {
117         while (m_srcIndex < srcLength - 1) {
118             QString unit = advance();
119             const QString& prefix = unit.toLower();
120             if (prefix == QLatin1String("sysutils") || prefix == QLatin1String("types") || prefix == QLatin1String("classes") ||
121                 prefix == QLatin1String("graphics") || prefix == QLatin1String("controls") || prefix == QLatin1String("strings") ||
122                 prefix == QLatin1String("forms") || prefix == QLatin1String("windows") || prefix == QLatin1String("messages") ||
123                 prefix == QLatin1String("variants") || prefix == QLatin1String("stdctrls") || prefix == QLatin1String("extctrls") ||
124                 prefix == QLatin1String("activex") || prefix == QLatin1String("comobj") || prefix == QLatin1String("registry") ||
125                 prefix == QLatin1String("classes") || prefix == QLatin1String("dialogs")) {
126                 if (advance() != QLatin1String(","))
127                     break;
128                 continue;
129             }
130             QString filename = unit + QLatin1String(".pas");
131             if (! m_parsedFiles.contains(unit)) {
132                 // Save current m_source and m_srcIndex.
133                 QStringList source(m_source);
134                 uint srcIndex = m_srcIndex;
135                 m_source.clear();
136                 parseFile(filename);
137                 // Restore m_source and m_srcIndex.
138                 m_source = source;
139                 m_srcIndex = srcIndex;
140                 // Also reset m_currentAccess.
141                 // CHECK: need to reset more stuff?
142                 m_currentAccess = Uml::Visibility::Public;
143             }
144             if (advance() != QLatin1String(","))
145                 break;
146         }
147         return true;
148     }
149     if (keyword == QLatin1String("unit")) {
150         const QString& name = advance();
151         UMLObject *ns = Import_Utils::createUMLObject(UMLObject::ot_Package, name,
152                                                       currentScope(), m_comment);
153         pushScope(ns->asUMLPackage());
154         skipStmt();
155         return true;
156     }
157     if (keyword == QLatin1String("interface")) {
158         m_inInterface = true;
159         return true;
160     }
161     if (keyword == QLatin1String("initialization") || keyword == QLatin1String("implementation")) {
162         m_inInterface = false;
163         return true;
164     }
165     if (! m_inInterface) {
166         // @todo parseStmt() should support a notion for "quit parsing, close file immediately"
167         return false;
168     }
169     if (keyword == QLatin1String("label")) {
170         m_section = sect_LABEL;
171         return true;
172     }
173     if (keyword == QLatin1String("const")) {
174         m_section = sect_CONST;
175         return true;
176     }
177     if (keyword == QLatin1String("resourcestring")) {
178         m_section = sect_RESOURCESTRING;
179         return true;
180     }
181     if (keyword == QLatin1String("type")) {
182         m_section = sect_TYPE;
183         return true;
184     }
185     if (keyword == QLatin1String("var")) {
186         m_section = sect_VAR;
187         return true;
188     }
189     if (keyword == QLatin1String("threadvar")) {
190         m_section = sect_THREADVAR;
191         return true;
192     }
193     if (keyword == QLatin1String("automated") || keyword == QLatin1String("published")  // no concept in UML
194      || keyword == QLatin1String("public")) {
195         m_currentAccess = Uml::Visibility::Public;
196         return true;
197     }
198     if (keyword == QLatin1String("protected")) {
199         m_currentAccess = Uml::Visibility::Protected;
200         return true;
201     }
202     if (keyword == QLatin1String("private")) {
203         m_currentAccess = Uml::Visibility::Private;
204         return true;
205     }
206     if (keyword == QLatin1String("packed")) {
207         return true;  // TBC: perhaps this could be stored in a TaggedValue
208     }
209     if (keyword == QLatin1String("[")) {
210         skipStmt(QLatin1String("]"));
211         return true;
212     }
213     if (keyword == QLatin1String("end")) {
214         if (m_klass) {
215             m_klass = 0;
216         } else if (scopeIndex()) {
217             popScope();
218             m_currentAccess = Uml::Visibility::Public;
219         } else {
220             uError() << "importPascal: too many \"end\"";
221         }
222         skipStmt();
223         return true;
224     }
225     if (keyword == QLatin1String("function") || keyword == QLatin1String("procedure") ||
226         keyword == QLatin1String("constructor") || keyword == QLatin1String("destructor")) {
227         if (m_klass == 0) {
228             // Unlike a Pascal unit, a UML package does not support subprograms.
229             // In order to map those, we would need to create a UML class with
230             // stereotype <<utility>> for the unit, https://bugs.kde.org/89167
231             bool dummyVirtual = false;
232             bool dummyAbstract = false;
233             checkModifiers(dummyVirtual, dummyAbstract);
234             return true;
235         }
236         const QString& name = advance();
237         UMLOperation *op = Import_Utils::makeOperation(m_klass, name);
238         if (m_source[m_srcIndex + 1] == QLatin1String("(")) {
239             advance();
240             const uint MAX_PARNAMES = 16;
241             while (m_srcIndex < srcLength && m_source[m_srcIndex] != QLatin1String(")")) {
242                 QString nextToken = m_source[m_srcIndex + 1].toLower();
243                 Uml::ParameterDirection::Enum dir = Uml::ParameterDirection::In;
244                 if (nextToken == QLatin1String("var")) {
245                     dir = Uml::ParameterDirection::InOut;
246                     advance();
247                 } else if (nextToken == QLatin1String("const")) {
248                     advance();
249                 } else if (nextToken == QLatin1String("out")) {
250                     dir = Uml::ParameterDirection::Out;
251                     advance();
252                 }
253                 QString parName[MAX_PARNAMES];
254                 uint parNameCount = 0;
255                 do {
256                     if (parNameCount >= MAX_PARNAMES) {
257                         uError() << "MAX_PARNAMES is exceeded at " << name;
258                         break;
259                     }
260                     parName[parNameCount++] = advance();
261                 } while (advance() == QLatin1String(","));
262                 if (m_source[m_srcIndex] != QLatin1String(":")) {
263                     uError() << "importPascal: expecting ':' at " << m_source[m_srcIndex];
264                     skipStmt();
265                     break;
266                 }
267                 nextToken = advance();
268                 if (nextToken.toLower() == QLatin1String("array")) {
269                     nextToken = advance().toLower();
270                     if (nextToken != QLatin1String("of")) {
271                         uError() << "importPascal(" << name << "): expecting 'array OF' at "
272                                   << nextToken;
273                         skipStmt();
274                         return false;
275                     }
276                     nextToken = advance();
277                 }
278                 for (uint i = 0; i < parNameCount; ++i) {
279                     UMLAttribute *att = Import_Utils::addMethodParameter(op, nextToken, parName[i]);
280                     att->setParmKind(dir);
281                 }
282                 if (advance() != QLatin1String(";"))
283                     break;
284             }
285         }
286         bool isConstructor = false;
287         bool isDestructor = false;
288         QString returnType;
289         if (keyword == QLatin1String("function")) {
290             if (advance() != QLatin1String(":")) {
291                 uError() << "importPascal: expecting \":\" at function "
292                         << name;
293                 return false;
294             }
295             returnType = advance();
296         } else if (keyword == QLatin1String("constructor")) {
297             isConstructor = true;
298         } else if (keyword == QLatin1String("destructor")) {
299             isDestructor = true;
300         }
301         skipStmt();
302         bool isVirtual = false;
303         bool isAbstract = false;
304         checkModifiers(isVirtual, isAbstract);
305         Import_Utils::insertMethod(m_klass, op, m_currentAccess, returnType,
306                                    !isVirtual, isAbstract, false, isConstructor,
307                                    isDestructor, m_comment);
308         return true;
309     }
310     if (m_section != sect_TYPE) {
311         skipStmt();
312         return true;
313     }
314     if (m_klass == 0) {
315         const QString& name = m_source[m_srcIndex];
316         QString nextToken = advance();
317         if (nextToken != QLatin1String("=")) {
318             uDebug() << name << ": expecting '=' at " << nextToken;
319             return false;
320         }
321         keyword = advance().toLower();
322         if (keyword == QLatin1String("(")) {
323             // enum type
324             UMLObject *ns = Import_Utils::createUMLObject(UMLObject::ot_Enum,
325                             name, currentScope(), m_comment);
326             UMLEnum *enumType = ns->asUMLEnum();
327             if (enumType == 0)
328                 enumType = Import_Utils::remapUMLEnum(ns, currentScope());
329             while (++m_srcIndex < srcLength && m_source[m_srcIndex] != QLatin1String(")")) {
330                 if (enumType != 0)
331                     Import_Utils::addEnumLiteral(enumType, m_source[m_srcIndex]);
332                 if (advance() != QLatin1String(","))
333                     break;
334             }
335             skipStmt();
336             return true;
337         }
338         if (keyword == QLatin1String("set")) {  // @todo implement Pascal set types
339             skipStmt();
340             return true;
341         }
342         if (keyword == QLatin1String("array")) {  // @todo implement Pascal array types
343             skipStmt();
344             return true;
345         }
346         if (keyword == QLatin1String("file")) {  // @todo implement Pascal file types
347             skipStmt();
348             return true;
349         }
350         if (keyword == QLatin1String("^")) {  // @todo implement Pascal pointer types
351             skipStmt();
352             return true;
353         }
354         if (keyword == QLatin1String("class") || keyword == QLatin1String("interface")) {
355             UMLObject::ObjectType t = (keyword == QLatin1String("class") ? UMLObject::ot_Class
356                                                                          : UMLObject::ot_Interface);
357             UMLObject *ns = Import_Utils::createUMLObject(t, name,
358                                                           currentScope(), m_comment);
359             UMLClassifier *klass = ns->asUMLClassifier();
360             m_comment.clear();
361             QString lookAhead = m_source[m_srcIndex + 1];
362             if (lookAhead == QLatin1String("(")) {
363                 advance();
364                 do {
365                     QString base = advance();
366                     UMLObject *ns = Import_Utils::createUMLObject(UMLObject::ot_Class, base, 0);
367                     UMLClassifier *parent = ns->asUMLClassifier();
368                     m_comment.clear();
369                     Import_Utils::createGeneralization(klass, parent);
370                 } while (advance() == QLatin1String(","));
371                 if (m_source[m_srcIndex] != QLatin1String(")")) {
372                     uError() << "PascalImport: expecting \")\" at "
373                         << m_source[m_srcIndex];
374                     return false;
375                 }
376                 lookAhead = m_source[m_srcIndex + 1];
377             }
378             if (lookAhead == QLatin1String(";")) {
379                 skipStmt();
380                 return true;
381             }
382             if (lookAhead == QLatin1String("of")) {
383                 // @todo implement class-reference type
384                 return false;
385             }
386             m_klass = klass;
387             m_currentAccess = Uml::Visibility::Public;
388             return true;
389         }
390         if (keyword == QLatin1String("record")) {
391             UMLObject *ns = Import_Utils::createUMLObject(UMLObject::ot_Class, name,
392                                                           currentScope(), m_comment);
393             ns->setStereotype(QLatin1String("record"));
394             m_klass = ns->asUMLClassifier();
395             return true;
396         }
397         if (keyword == QLatin1String("function") || keyword == QLatin1String("procedure")) {
398             /*UMLObject *ns =*/ Import_Utils::createUMLObject(UMLObject::ot_Datatype, name,
399                                                           currentScope(), m_comment);
400             if (m_source[m_srcIndex + 1] == QLatin1String("("))
401                 skipToClosing(QLatin1Char('('));
402             skipStmt();
403             return true;
404         }
405         // Datatypes: TO BE DONE
406         return false;
407     }
408     // At this point we need a class because we're expecting its member attributes.
409     if (m_klass == 0) {
410         uDebug() << "importPascal: skipping " << m_source[m_srcIndex];
411         skipStmt();
412         return true;
413     }
414     QString name, stereotype;
415     if (keyword == QLatin1String("property")) {
416         stereotype = keyword;
417         name = advance();
418     } else {
419         name = m_source[m_srcIndex];
420     }
421     if (advance() != QLatin1String(":")) {
422         uError() << "PascalImport: expecting \":\" at " << name << " "
423                  << m_source[m_srcIndex];
424         skipStmt();
425         return true;
426     }
427     QString typeName = advance();
428     QString initialValue;
429     if (advance() == QLatin1String("=")) {
430         initialValue = advance();
431         QString token;
432         while ((token = advance()) != QLatin1String(";")) {
433             initialValue.append(QLatin1Char(' ') + token);
434         }
435     }
436     UMLObject *o = Import_Utils::insertAttribute(m_klass, m_currentAccess, name,
437                                                  typeName, m_comment);
438     UMLAttribute *attr = o->asUMLAttribute();
439     attr->setStereotype(stereotype);
440     attr->setInitialValue(initialValue);
441     skipStmt();
442     return true;
443 }
444 
445 
446