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