File indexing completed on 2024-12-08 07:18:26
0001 #!/usr/bin/env python 0002 # -*- coding: utf-8 -*- 0003 from __future__ import print_function, unicode_literals 0004 # 0005 # This file is part of the KDE project 0006 # Copyright (C) 2010-2015 Jarosław Staniek <staniek@kde.org> 0007 # 0008 # Shared Data Compiler (SDC) 0009 0010 version = '0.3' 0011 0012 # A tool that takes a header file with C++ class declaration with specification 0013 # of class' data members and injects getters, setters and facilities for 0014 # Qt 5-compatible implicitly/explicitly sharing. Tedious, manual work is considerably 0015 # reduced. The generated code is enriched with Doxygen-style documentation. 0016 # 0017 # Syntax: 0018 # Add an //SDC: comment to the class declaration line 0019 # class MYLIB_EXPORT MyClass [: public SuperClass] //SDC: [CLASS_OPTIONS] 0020 # 0021 # supported CLASS_OPTIONS: namespace=NAMESPACE, with_from_to_map, 0022 # operator==, explicit, virtual_dtor, custom_clone 0023 # 0024 # TODO: explain details in README-SDC.md 0025 # 0026 # Specification of each class' data members should be in form: 0027 # 0028 # TYPE NAME; //SDC: [DATA_MEMBER_OPTIONS] 0029 # 0030 # Supported DATA_MEMBER_OPTIONS: default=DEFAULT_VALUE, no_getter, no_setter, 0031 # getter=CUSTOM_GETTER_NAME, 0032 # setter=CUSTOM_SETTER_NAME, 0033 # custom, custom_getter, custom_setter, 0034 # default_setter=DEFAULT_SETTER_PARAM, 0035 # mutable, simple_type, invokable, 0036 # internal, custom_clone 0037 # If NAME contains '(' then 'TYPE NAME;' is added to the shared data class 0038 # as a method declaration. 0039 # 0040 # This program is free software; you can redistribute it and/or 0041 # modify it under the terms of the GNU General Public 0042 # License as published by the Free Software Foundation; either 0043 # version 2 of the License, or (at your option) any later version. 0044 # 0045 # This program is distributed in the hope that it will be useful, 0046 # but WITHOUT ANY WARRANTY; without even the implied warranty of 0047 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0048 # Library General Public License for more details. 0049 # 0050 # You should have received a copy of the GNU Library General Public License 0051 # along with this program; see the file COPYING. If not, write to 0052 # the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0053 # Boston, MA 02110-1301, USA. 0054 # 0055 # As a special exception, you may create a larger work that contains 0056 # code generated by the Shared Data Compiler and distribute that work 0057 # under terms of the GNU Lesser General Public License (LGPL) as published 0058 # by the Free Software Foundation; either version 2.1 of the License, 0059 # or (at your option) any later version or under terms that are fully 0060 # compatible with these licenses. 0061 # 0062 # Alternatively, if you modify or redistribute the Shared Data Compiler tool 0063 # itself, you may (at your option) remove this special exception, which will 0064 # cause the resulting generted source code files to be licensed under 0065 # the GNU General Public License (either version 2 of the License, or 0066 # at your option under any later version) without this special exception. 0067 # 0068 # This special exception was added by Jarosław Staniek. 0069 # Contact him for more licensing options, e.g. using in non-Open Source projects. 0070 # 0071 0072 import os, sys, shlex 0073 0074 line = '' 0075 0076 APPNAME = "Shared Data Compiler" 0077 0078 def usage(): 0079 print('''Usage: %s [INPUT] [OUTPUT] 0080 %s version %s 0081 ''' % (sys.argv[0], APPNAME, version)) 0082 0083 def syntax_error(msg): 0084 print(APPNAME, "Syntax error in %s: %s" % (in_fname, msg), file=sys.stderr) 0085 sys.exit(1) 0086 0087 def warning(*objs): 0088 print(APPNAME, "WARNING:", *objs, file=sys.stderr) 0089 0090 if len(sys.argv) < 3: 0091 usage() 0092 sys.exit(0) 0093 0094 # --- open --- 0095 in_fname = sys.argv[1] 0096 out_fname = sys.argv[2] 0097 0098 try: 0099 infile = open(in_fname, "rb") # binary mode needed for Windows 0100 outfile = open(out_fname, "wb") 0101 except Exception as inst: 0102 print(inst) 0103 sys.exit(1) 0104 0105 outfile_sdc = None 0106 0107 # --- utils --- 0108 def write(f, line): 0109 f.write(line.encode('utf-8')) 0110 0111 def param(lst, name): 0112 for item in lst: 0113 s = item.split('=') 0114 if len(s) > 1 and s[0] == name: 0115 return s[1] 0116 return '' 0117 0118 def param_exists(lst, name): 0119 try: 0120 if lst.index(name) >= 0: 0121 return True 0122 except ValueError: 0123 pass 0124 return False 0125 0126 def find_index(list, elem): 0127 try: 0128 i = list.index(elem) 0129 except ValueError: 0130 i = -1 0131 return i 0132 0133 def find_last_index(list, elem): 0134 i = find_index(list[::-1], elem) #reverted 0135 return -1 if i == -1 else len(list) - i - 1 0136 0137 # --- process --- 0138 shared_class_name = '' 0139 shared_class_options = {} 0140 generated_code_inserted = False 0141 shared_class_inserted = False 0142 data_class_ctor = '' 0143 data_class_copy_ctor = '' 0144 data_class_members = '' 0145 members_list = [] 0146 data_accesors = '' 0147 protected_data_accesors = '' 0148 main_ctor = '' 0149 member = {} 0150 toMap_impl = '' 0151 fromMap_impl = '' 0152 0153 def get_file(fname): 0154 if fname.rfind(os.sep) == -1: 0155 return fname 0156 return fname[fname.rfind(os.sep)+1:] 0157 0158 def open_sdc(): 0159 global outfile_sdc, shared_class_options 0160 if not outfile_sdc: 0161 sdc_fname = out_fname.replace('.h', '_sdc.cpp') 0162 try: 0163 outfile_sdc = open(sdc_fname, "wb") 0164 except Exception as inst: 0165 print(inst) 0166 sys.exit(1) 0167 write(outfile_sdc, warningHeader()) 0168 write(outfile_sdc, """#include "%s" 0169 #include <QVariant> 0170 0171 """ % get_file(out_fname)) 0172 0173 if shared_class_options['namespace']: 0174 write(outfile_sdc, """using namespace %s; 0175 0176 """ % shared_class_options['namespace']) 0177 0178 """ 0179 Inserts generated fromMap(), toMap() code into the output. 0180 Declarations are inserted into the header, definitions into extra *_sdc.cpp file 0181 """ 0182 def insert_fromMap_toMap_methods(): 0183 global outfile, shared_class_name, toMap_impl, fromMap_impl 0184 write(outfile, """ /*! @return map with saved attributes of the %s object. 0185 0186 @see %s(const QMap<QString, QString>&, bool *). 0187 */ 0188 QMap<QString, QString> toMap() const { 0189 return d->toMap(); 0190 } 0191 0192 """ % (shared_class_name, shared_class_name)) 0193 open_sdc() 0194 write(outfile_sdc, """%s::Data::Data(const QMap<QString, QString> &map, bool *ok) 0195 { 0196 if (ok) { 0197 *ok = true; 0198 } 0199 %s} 0200 0201 QMap<QString, QString> %s::Data::toMap() const 0202 { 0203 QMap<QString, QString> map; 0204 %s return map; 0205 } 0206 """ % (shared_class_name, fromMap_impl, shared_class_name, toMap_impl)) 0207 outfile_sdc.close() 0208 0209 """ 0210 Inserts generated operator==() code for shared class into the output. 0211 """ 0212 def insert_operator_eq(): 0213 global outfile, shared_class_name, shared_class_options, superclass 0214 data = 'data()' if superclass else 'd' 0215 otherData = 'other.' + data 0216 if not shared_class_options['explicit']: # deep comparison only for implicitly shared data 0217 data = '*' + data 0218 otherData = '*' + otherData 0219 write(outfile, """ //! @return true if this object is equal to @a other; otherwise returns false. 0220 bool operator==(const %s &other) const { 0221 return %s == %s; 0222 } 0223 0224 """ % (shared_class_name, data, otherData)) 0225 0226 """ 0227 Inserts generated operator!=() code for shared class into the output. 0228 """ 0229 def insert_operator_neq(): 0230 global outfile, shared_class_name, shared_class_options, superclass 0231 write(outfile, """ //! @return true if this object is not equal to @a other; otherwise returns false. 0232 bool operator!=(const %s &other) const { 0233 return !operator==(other); 0234 } 0235 0236 """ % (shared_class_name)) 0237 0238 """ 0239 Prepends 'virtual' to the method if there is superclass, otherwise appends 'override'. 0240 """ 0241 def virtual_or_override(hasSuperclass, method): 0242 return ('virtual ' if not hasSuperclass else '') + method + (' override' if hasSuperclass else '') 0243 0244 """ 0245 Inserts generated clone() method (makes sense for explicitly shared class). 0246 """ 0247 def insert_clone(): 0248 global outfile, shared_class_name, shared_class_options, superclass 0249 line = """ //! Clones the object with all attributes; the copy isn't shared with the original. 0250 %s""" % virtual_or_override(superclass, shared_class_name + ' clone() const') 0251 custom_clone = False; # not needed I guess: shared_class_options['custom_clone'] 0252 if custom_clone: 0253 line += """; 0254 """ 0255 else: 0256 line += """ { 0257 return %s(d->clone()); 0258 } 0259 """ % (shared_class_name) 0260 write(outfile, line) 0261 0262 """ 0263 Inserts generated Data::operator==() code into the output. 0264 """ 0265 def insert_data_operator_eq(): 0266 global outfile, members_list, superclass 0267 write(outfile, """ bool operator==(const Data &other) const { 0268 """) 0269 write(outfile, " return ") 0270 first = True; 0271 space = """ 0272 && """ 0273 if superclass: 0274 write(outfile, '%s::Data::operator==(other)' % superclass) 0275 first = False; 0276 for member in members_list: 0277 if member['internal']: 0278 continue 0279 write(outfile, """%s%s == other.%s""" % ('' if first else space, member['name'], member['name'])) 0280 if first: 0281 first = False 0282 write(outfile, """; 0283 } 0284 0285 """) 0286 0287 """ 0288 Inserts generated code into the output. 0289 """ 0290 def insert_generated_code(context): 0291 global infile, outfile, generated_code_inserted, data_class_ctor, data_class_copy_ctor, superclass 0292 global data_class_members, data_accesors, protected_data_accesors, main_ctor, shared_class_name, shared_class_options 0293 global prev_line 0294 if generated_code_inserted: 0295 return; 0296 #print "--------insert_generated_code--------" 0297 #write(outfile, '//CONTEXT:' + str(context) + '\n') 0298 #write(outfile, '//data_class_ctor>\n') 0299 write(outfile, data_class_ctor) 0300 #write(outfile, '//<data_class_ctor\n') 0301 write(outfile,""" { 0302 } 0303 """) 0304 write(outfile, data_class_copy_ctor) 0305 write(outfile, """ { 0306 } 0307 0308 """) 0309 write(outfile, """ %s {} 0310 0311 """ % virtual_or_override(superclass, '~Data()')) 0312 write(outfile, """ //! Clones the object with all attributes; the copy isn't shared with the original. 0313 %s""" % virtual_or_override(superclass, ((superclass + '::') if superclass else '') + "Data* clone() const")) 0314 if shared_class_options['custom_clone']: 0315 write(outfile, """; 0316 0317 """) 0318 else: 0319 write(outfile, """ { return new Data(*this); } 0320 0321 """) 0322 0323 if shared_class_options['with_from_to_map']: 0324 write(outfile, """ /*! Constructor for Data object, takes attributes saved to map @a map. 0325 If @a ok is not 0, *ok is set to true on success and to false on failure. @see toMap(). */ 0326 Data(const QMap<QString, QString> &map, bool *ok); 0327 0328 QMap<QString, QString> toMap() const; 0329 0330 """) 0331 if shared_class_options['operator=='] and not shared_class_options['explicit']: 0332 insert_data_operator_eq() 0333 0334 write(outfile, data_class_members) 0335 write(outfile, main_ctor) 0336 write(outfile, data_accesors) 0337 write(outfile, "\n") 0338 if shared_class_options['with_from_to_map']: 0339 insert_fromMap_toMap_methods() 0340 if shared_class_options['operator==']: 0341 insert_operator_eq() 0342 insert_operator_neq() 0343 if shared_class_options['explicit'] and (not superclass or shared_class_options['custom_clone']): 0344 insert_clone() 0345 if protected_data_accesors: 0346 write(outfile, "protected:") 0347 write(outfile, protected_data_accesors) 0348 write(outfile, "\npublic:") 0349 write(outfile, "\n") 0350 generated_code_inserted = True 0351 0352 0353 """ 0354 Reads documentation for single section (setter or getter) and returns it. 0355 Leaves the file pointer before */ or another @getter/@setter mark. 0356 """ 0357 def read_getter_or_setter_doc(): 0358 global prev_line, line 0359 result = '' 0360 while True: 0361 prev_pos = infile.tell() 0362 prev_line = line 0363 line = infile.readline().decode('utf-8') 0364 if not line: 0365 break 0366 elif line.find('*/') != -1 or line.find('@getter') != -1 or line.find('@setter') != -1: 0367 #print "seek prev from " + line 0368 infile.seek(prev_pos) 0369 break 0370 else: 0371 result += line 0372 return result 0373 0374 def process_docs(comment): 0375 global prev_line, line 0376 result = {} 0377 while True: 0378 prev_line = line 0379 line = infile.readline().decode('utf-8') 0380 #print "process_docs: " + line 0381 if not line: 0382 break 0383 elif line.find('*/') != -1: 0384 if result == {}: 0385 insert_generated_code(1) 0386 write(outfile, line) 0387 break 0388 elif line.find('@getter') != -1: 0389 result['getter'] = read_getter_or_setter_doc() 0390 elif line.find('@setter') != -1: 0391 result['setter'] = read_getter_or_setter_doc() 0392 else: 0393 insert_generated_code(2) 0394 write(outfile, comment) 0395 write(outfile, line) 0396 if result == {}: 0397 result = None 0398 #print "process_docs result: " + str(result) 0399 return result 0400 0401 def try_read_member_docs(comment): 0402 global prev_line, line 0403 prev_pos = infile.tell() 0404 result = comment 0405 while True: 0406 prev_line = line 0407 line = infile.readline().decode('utf-8') 0408 if not line or line.find('@getter') != -1 or line.find('@setter') != -1: 0409 infile.seek(prev_pos) 0410 return None 0411 elif line.find('*/') != -1: 0412 return result 0413 else: 0414 result += line 0415 return None 0416 0417 """ makes setter out of name or returns forceSetter if specified """ 0418 def makeSetter(name, forceSetter): 0419 if forceSetter: 0420 return forceSetter 0421 return 'set' + name[0].upper() + name[1:] 0422 0423 def update_data_accesors(): 0424 global data_accesors, protected_data_accesors, member 0425 if not member['no_getter']: 0426 if 'getter_docs' in member: 0427 val = '\n /*!\n' + member['getter_docs'] + ' */' 0428 if member['access'] == 'public': 0429 data_accesors += val 0430 else: # protected 0431 protected_data_accesors += val 0432 getter = member['getter'] 0433 if not getter: 0434 getter = member['name'] 0435 invokable = 'Q_INVOKABLE ' if member['invokable'] else '' 0436 if member['custom_getter']: 0437 val = """ 0438 %s%s %s() const; 0439 """ % (invokable, member['type'], getter) 0440 else: 0441 val = """ 0442 %s%s %s() const { 0443 return %s->%s; 0444 } 0445 """ % (invokable, member['type'], getter, 'data()' if superclass else 'd', member['name']) 0446 if member['access'] == 'public': 0447 data_accesors += val 0448 else: # protected 0449 protected_data_accesors += val 0450 if not member['no_setter']: 0451 if 'setter_docs' in member: 0452 val = '\n /*!\n' + member['setter_docs'] + ' */' 0453 if member['access'] == 'public': 0454 data_accesors += val 0455 else: # protected 0456 protected_data_accesors += val 0457 # heuristics to check if the const & should be used: 0458 arg_type = member['type'] 0459 if arg_type.lower() != arg_type and not member['simple_type']: 0460 arg_type = 'const %s &' % arg_type 0461 setter = makeSetter(member['name'], member['setter']) 0462 default_setter = (' = ' + member['default_setter']) if member['default_setter'] else '' 0463 invokable = 'Q_INVOKABLE ' if member['invokable'] else '' 0464 if member['custom_setter']: 0465 val = """ 0466 %svoid %s(%s %s%s); 0467 """ % (invokable, setter, arg_type, member['name'], default_setter) 0468 else: 0469 val = """ 0470 %svoid %s(%s %s%s) { 0471 %s->%s = %s; 0472 } 0473 """ % (invokable, setter, arg_type, member['name'], default_setter, 'data()' if superclass else 'd', member['name'], member['name']) 0474 if member['access'] == 'public': 0475 data_accesors += val 0476 else: # protected 0477 protected_data_accesors += val 0478 0479 def data_member_found(lst): 0480 return len(lst) > 2 and lst[0] != 'class' and find_index(lst, '//SDC:') >= 2 0481 0482 # sets shared_class_options[option_name] to proper value; returns lst with removed element option_name if exists 0483 def get_shared_class_option(lst, option_name): 0484 global shared_class_options 0485 shared_class_options[option_name] = param_exists(lst, option_name) 0486 if shared_class_options[option_name]: 0487 lst.remove(option_name) 0488 return lst 0489 0490 """ like get_shared_class_option() but also gets value (not just checks for existence) """ 0491 def get_shared_class_option_with_value(lst, option_name): 0492 global shared_class_options 0493 for item in lst: 0494 s = item.split('=') 0495 if len(s) > 1 and s[0] == option_name: 0496 lst.remove(item) 0497 shared_class_options[option_name] = s[1] 0498 return lst 0499 shared_class_options[option_name] = False 0500 return lst 0501 0502 def enabled_shared_class_options(): 0503 global shared_class_options 0504 result = [] 0505 for opt in shared_class_options: 0506 if shared_class_options[opt]: 0507 result.append(opt) 0508 # add some defaults 0509 if find_index(result, 'explicit') == -1: 0510 result.append('implicit') 0511 return result 0512 0513 def warningHeader(): 0514 return """/**************************************************************************** 0515 ** Shared Class code from reading file '%s' 0516 ** 0517 ** Created 0518 ** by: The Shared Data Compiler version %s 0519 ** 0520 ** WARNING! All changes made in this file will be lost! 0521 *****************************************************************************/ 0522 0523 """ % (get_file(in_fname), version) 0524 0525 """ generates conversion code to string from many types, used by Data::toMap() 0526 @todo more types """ 0527 def generate_toString_conversion(name, _type): 0528 if _type == 'QString' or _type == 'QByteArray': 0529 return name 0530 elif _type == 'bool': 0531 return 'QString::number((int)%s)' % name # 0 or 1 0532 return 'QVariant(%s).toString()' % name 0533 0534 """ generates conversion code from string to many types, used by Data(QMap<..>) 0535 @todo more types """ 0536 def generate_fromString_conversion(name, _type): 0537 s = 'map[QLatin1String(\"%s\")]' % name 0538 if _type == 'bool': # 0 or 1 0539 return """bool %sOk; 0540 %s = %s.toInt(&%sOk) == 1; 0541 if (!%sOk && ok) { 0542 *ok = false; 0543 }""" % (name, name, s, name, name) 0544 elif _type == 'int': 0545 return """bool %sOk; 0546 %s = %s.toInt(&%sOk); 0547 if (!%sOk && ok) { 0548 *ok = false; 0549 }""" % (name, name, s, name, name) 0550 else: # QString... 0551 return "%s = %s;" % (name, s) 0552 0553 # returns position (line number) for #include <QSharedData> or -1 if #include <QSharedData> isn't needed 0554 def get_pos_for_QSharedData_h(): 0555 global infile, prev_line, line 0556 prev_pos = infile.tell() 0557 line_number = -1 0558 infile.seek(0) 0559 # find last #include 0560 last_include = -1 0561 while True: 0562 prev_line = line 0563 line = infile.readline().decode('utf-8').lower() 0564 if not line: 0565 break 0566 line_number += 1 0567 if line.find('#include ') != -1: 0568 if line.find('qshareddata') != -1: 0569 last_include = -1 0570 break 0571 else: 0572 last_include = line_number + 1 0573 infile.seek(prev_pos) 0574 return last_include 0575 0576 # replaces "Foo<ABC<DEF>>" with "Foo< ABC< DEF > >" to avoid build errors 0577 def fix_templates(s): 0578 if s.count('<') < 2: 0579 return s 0580 result='' 0581 for c in s: 0582 if c == '>': 0583 result += ' ' 0584 result += c 0585 if c == '<': 0586 result += ' ' 0587 return result 0588 0589 def other_comment(line): 0590 ln = line.strip(' ') 0591 return ln.startswith('/**') \ 0592 or ln.startswith('/*!') \ 0593 or ln .startswith('//!') \ 0594 or ln.startswith('///') 0595 0596 """ @return (shared_class_name, export_macro, superclass) """ 0597 def get_shared_class_name_export_and_superclass(lst): 0598 # if 'lst' contains ':' and then 'public', we have inheritance 0599 i = find_last_index(lst, 'public') # last index because there can be multiple 'public/protected' and most likely we want the last 0600 #print i 0601 if i >= 3: 0602 inheritance_type = 'public' 0603 else: 0604 i = find_last_index(lst, 'protected') 0605 inheritance_type = 'protected' if i >= 3 else '' 0606 # - if there's inheritance and export_macro exists, lst has at least 6 elements: 0607 # ['class', export_macro, shared_class_name, ':', 'public/protected', superclass, ...]: 0608 # - if export_macro exists, lst has at least 3 elements: 0609 # ['class', export_macro, shared_class_name, ...]: 0610 expected_len = 6 if inheritance_type else 3 0611 if len(lst) >= expected_len: 0612 _shared_class_name = lst[2] 0613 _export_macro = lst[1] 0614 else: # no export_macro 0615 _shared_class_name = lst[1] 0616 _export_macro = '' 0617 _superclass = lst[i + 1] if inheritance_type else '' 0618 return (_shared_class_name, _export_macro, _superclass) 0619 0620 """ Strips the C++ comment //: @return lst with removed first found element that starts 0621 with '//' and all following """ 0622 def remove_cpp_comment(lst): 0623 result = [] 0624 for el in lst: 0625 if el.startswith('//'): 0626 break 0627 result.append(el) 0628 return result 0629 0630 """ Simplifies a multi-line doc like this: 0631 /*! 0632 Foo 0633 */ 0634 to: 0635 /*! Foo */ """ 0636 def simplify_multiline_doc(doc): 0637 lines = doc.split('\n') 0638 result = [] 0639 if lines[0].strip() == '': 0640 result += '\n' 0641 lines.pop(0) 0642 if len(lines) < 2: 0643 return doc 0644 indentation = ''; 0645 i = 0; 0646 for c in lines[0]: 0647 if c == ' ' or c == '\t': 0648 indentation += c 0649 else: 0650 break 0651 line0 = lines[0].strip() 0652 line1 = lines[1].strip() 0653 if line0 == '/*!' and not line1.startswith('/*') and not line1.startswith('/*'): 0654 result += indentation + '/*! ' + line1 + '\n' 0655 lines = lines[2:] # because line 0 and 1 already processed 0656 for line in lines: 0657 if line.strip() == '*/': # add to prev line 0658 last = result.pop() 0659 result += last[:-1] + ' */\n' 0660 else: 0661 result += line + '\n' 0662 result[-1] = result[-1][:-1] # remove last \n 0663 return ''.join(result) 0664 0665 def process(): 0666 global infile, outfile, generated_code_inserted, data_class_ctor, data_class_copy_ctor 0667 global shared_class_name, superclass, shared_class_options, shared_class_inserted, data_class_members 0668 global members_list, data_accesors, member, main_ctor, toMap_impl, fromMap_impl 0669 global prev_line, line 0670 write(outfile, warningHeader()) 0671 0672 member = {} 0673 after_member = False 0674 0675 data_class_ctor = '' 0676 data_class_copy_ctor = '' 0677 data_class_ctor_changed = False 0678 data_class_copy_ctor_changed = False 0679 position_for_include_QSharedData_h = get_pos_for_QSharedData_h() 0680 0681 line_number = -1 # used for writing #include <QSharedData> 0682 while True: 0683 prev_line = line 0684 line = infile.readline().decode('utf-8') 0685 line_number += 1 0686 if not line: 0687 break 0688 # print line, 0689 lst = line.split() 0690 #print lst, find_index(lst, '//SDC:') 0691 if line_number == position_for_include_QSharedData_h: 0692 write(outfile, """#include <QSharedData> 0693 """) 0694 position_for_include_QSharedData_h = -1 0695 line_starts_with_class = len(lst) > 0 and lst[0] == 'class' 0696 if line_starts_with_class and find_index(lst, '//SDC:') < 0: 0697 shared_class_inserted = False # required because otherwise QSharedDataPointer<Data> d will be added to all classes 0698 write(outfile, line) 0699 elif line_starts_with_class and find_index(lst, '//SDC:') > 0: 0700 # re-init variables, needed if there are more than one shared class per file 0701 shared_class_inserted = False 0702 generated_code_inserted = False 0703 shared_class_options = {} 0704 data_class_ctor = '' 0705 data_class_copy_ctor = '' 0706 data_class_members = '' 0707 members_list = [] 0708 data_accesors = '' 0709 protected_data_accesors = '' 0710 toMap_impl = '' 0711 fromMap_impl = '' 0712 data_class_ctor_changed = False 0713 data_class_copy_ctor_changed = False 0714 lst = shlex.split(line) # better than default split() 0715 # Syntax: shared class [EXPORT_MACRO] [CLASS_OPTIONS] MyClass [: public SuperClass] 0716 # Any unrecognized bits are left out, this lets to use EXPORT_MACRO=MYLIB_EXPORT for example. 0717 # output: class [EXPORT_MACRO] MyClass [: public SuperClass] 0718 lst = get_shared_class_option(lst, 'explicit') 0719 lst = get_shared_class_option(lst, 'operator==') 0720 lst = get_shared_class_option(lst, 'with_from_to_map') 0721 lst = get_shared_class_option(lst, 'virtual_dtor') 0722 lst = get_shared_class_option(lst, 'custom_clone') # only insert declaration of clone() 0723 lst = get_shared_class_option_with_value(lst, 'namespace') 0724 (shared_class_name, export_macro, superclass) = get_shared_class_name_export_and_superclass(lst) 0725 if superclass: 0726 shared_class_options['virtual_dtor'] = True # inheritance implies this 0727 if shared_class_options['custom_clone'] and not shared_class_options['explicit']: 0728 warning('\'custom_clone\' class option only supported with \'explicit\' class option') 0729 shared_class_options['custom_clone'] = False 0730 main_ctor = """ }; 0731 0732 %s() 0733 : %s(new Data) 0734 { 0735 } 0736 0737 %s(const %s &other) 0738 : %s 0739 { 0740 } 0741 """ % (shared_class_name, superclass if superclass else 'd', shared_class_name, shared_class_name, (superclass + '(other)') if superclass else 'd(other.d)') 0742 if superclass: 0743 main_ctor += """ 0744 %s(const %s &other) 0745 : %s(other) 0746 { 0747 if (!data()) { // '@a 'other' does not store suitable data, create a new one and copy what we have 0748 d = new Data(*d.data()); 0749 } 0750 } 0751 """ % (shared_class_name, superclass, superclass) 0752 0753 if shared_class_options['with_from_to_map']: 0754 main_ctor += """ 0755 /*! Constructor for %s object, takes attributes saved to map @a map. 0756 If @a ok is not 0, sets *ok to true on success and to false on failure. @see toMap(). */ 0757 %s(const QMap<QString, QString> &map, bool *ok) 0758 : d(new Data(map, ok)) 0759 { 0760 } 0761 """ % (shared_class_name, shared_class_name) 0762 main_ctor += """ 0763 %s; 0764 """ % virtual_or_override(superclass and shared_class_options['virtual_dtor'], '~' + shared_class_name + '()') 0765 if shared_class_options['explicit']: 0766 write(outfile, """/*! @note objects of this class are explicitly shared, what means they behave like regular 0767 C++ pointers, except that by doing reference counting and not deleting the shared 0768 data object until the reference count is 0, they avoid the dangling pointer problem. 0769 See <a href="https://doc.qt.io/qt-5/qexplicitlyshareddatapointer.html#details">Qt documentation</a>. 0770 */ 0771 """) 0772 else: 0773 write(outfile, """/*! @note objects of this class are implicitly shared, what means they have value semantics 0774 by offering copy-on-write behaviour to maximize resource usage and minimize copying. 0775 Only a pointer to the data is passed around. See <a href="https://doc.qt.io/qt-5/qshareddatapointer.html#details">Qt documentation</a>. 0776 */ 0777 """) 0778 0779 # Finally output the class name: use all remaining elements of 'lst' except 0780 # the 0th (which is the 'shared' keyword): 0781 write(outfile, '//! @note This class has been generated using the following SDC class options: ' 0782 + ', '.join(enabled_shared_class_options()) + '\n') 0783 write(outfile, ' '.join(remove_cpp_comment(lst)) + '\n') 0784 while True: 0785 prev_line = line 0786 line = infile.readline().decode('utf-8') # output everything until 'public:' 0787 write(outfile, line) 0788 if line.strip().startswith('public:'): 0789 break 0790 shared_class_inserted = True 0791 elif len(lst) >= 2 and lst[0] == '#if' and lst[1] == '0': 0792 insert_generated_code(3) 0793 write(outfile, line) 0794 while True: 0795 prev_line = line 0796 line = infile.readline().decode('utf-8') 0797 lst = line.split() 0798 if not line: 0799 break 0800 elif len(lst) >= 1 and lst[0] == '#endif': 0801 write(outfile, line) 0802 break 0803 write(outfile, line) 0804 elif len(lst) == 1 and (lst[0] == '/**' or lst[0] == '/*!'): 0805 comment = line 0806 0807 member_docs = try_read_member_docs(comment) 0808 #print "member_docs:" + str(member_docs) 0809 0810 docs = None 0811 if member_docs: 0812 member = {} 0813 member['docs'] = '\n ' + member_docs.replace('\n', '\n ') + ' */'; 0814 else: 0815 docs = process_docs(comment) 0816 if docs: 0817 #print "DOCS:" + str(docs) 0818 member = {} 0819 if 'getter' in docs: 0820 member['getter_docs'] = docs['getter'] 0821 if 'setter' in docs: 0822 member['setter_docs'] = docs['setter'] 0823 elif not member_docs: 0824 insert_generated_code(4) 0825 write(outfile, comment) 0826 elif len(lst) >= 2 and lst[0] == 'enum': 0827 # skip enums: until '};' found 0828 write(outfile, line) 0829 while True: 0830 prev_line = line 0831 line = infile.readline().decode('utf-8') 0832 lst = line.split() 0833 if len(lst) > 0 and lst[0] == '};': 0834 write(outfile, line) 0835 break 0836 write(outfile, line) 0837 elif data_member_found(lst): 0838 """ for syntax see top of the file """ 0839 if lst[1].endswith(';'): 0840 lst[1] = lst[1][:-1] # strip ';' from member name 0841 sdc_index = find_index(lst, '//SDC:') 0842 options_list = lst[sdc_index+1:] 0843 #print lst 0844 member['name'] = lst[1] 0845 member['type'] = fix_templates(lst[0]) 0846 member['access'] = 'protected' if (find_index(options_list, 'protected') >= 0) else 'public' 0847 member['default'] = param(options_list, 'default') 0848 member['default_setter'] = param(options_list, 'default_setter') 0849 member['internal'] = param_exists(options_list, 'internal'); # skip Data::operators and setter/getter 0850 member['no_getter'] = param_exists(options_list, 'no_getter') or member['internal'] 0851 member['getter'] = param(options_list, 'getter') 0852 member['no_setter'] = param_exists(options_list, 'no_setter') or member['internal'] 0853 member['setter'] = param(options_list, 'setter') 0854 member['custom'] = param_exists(options_list, 'custom') 0855 member['custom_getter'] = param_exists(options_list, 'custom_getter') or member['custom'] 0856 member['custom_setter'] = param_exists(options_list, 'custom_setter') or member['custom'] 0857 member['mutable'] = param_exists(options_list, 'mutable') 0858 member['simple_type'] = param_exists(options_list, 'simple_type') 0859 member['invokable'] = param_exists(options_list, 'invokable') 0860 # '(' found in name so it's a method declaration -> just add it to the data class 0861 isMethodDeclaration = find_index(member['name'], '(') >= 0 0862 isInternalMember = member['no_getter'] and member['no_setter'] 0863 #print member 0864 if not data_class_ctor_changed: 0865 data_class_ctor = """ //! @internal data class used to implement %s shared class %s. 0866 //! Provides thread-safe reference counting. 0867 class Data : public %s 0868 { 0869 public: 0870 Data() 0871 """ % ('explicitly' if shared_class_options['explicit'] else 'implicitly', shared_class_name, (superclass + '::Data') if superclass else 'QSharedData') 0872 if not data_class_copy_ctor_changed: 0873 data_class_copy_ctor = '' 0874 if superclass: 0875 data_class_copy_ctor += """ 0876 Data(const %s::Data &other) 0877 : %s::Data(other) 0878 { 0879 } 0880 """ % (superclass, superclass) 0881 data_class_copy_ctor += """ 0882 Data(const Data &other) 0883 : %s(other) 0884 """ % ((superclass + '::Data') if superclass else 'QSharedData') 0885 data_class_copy_ctor_changed = True 0886 if not isMethodDeclaration: 0887 members_list.append(member); 0888 if member['default']: 0889 data_class_ctor += ' ' 0890 if data_class_ctor_changed: 0891 data_class_ctor += ', ' 0892 else: 0893 data_class_ctor += ': ' 0894 data_class_ctor_changed = True 0895 data_class_ctor += member['name'] + '(' + member['default'] + ')\n' 0896 # print data_class_ctor 0897 if not member['internal']: 0898 data_class_copy_ctor += ' , %s(other.%s)\n' % (member['name'], member['name']) 0899 if 'docs' in member: 0900 data_class_members += simplify_multiline_doc(member['docs']) + '\n'; 0901 0902 mutable = 'mutable ' if member['mutable'] else '' 0903 data_class_members += " %s%s;" % (mutable, ' '.join(lst[:sdc_index])) 0904 # add doc for shared data member 0905 if not isMethodDeclaration: 0906 if isInternalMember: 0907 data_class_members += ' //!< @internal' 0908 else: 0909 data_class_members += ' //!< @see ' 0910 if not member['no_getter']: 0911 getter = member['getter'] 0912 if not getter: 0913 getter = member['name'] 0914 data_class_members += "%s::%s()" % (shared_class_name, getter) 0915 if not member['no_setter']: 0916 if not member['no_getter']: 0917 data_class_members += ", " 0918 setter = makeSetter(member['name'], member['setter']) 0919 data_class_members += "%s::%s()" % (shared_class_name, setter) 0920 0921 data_class_members += '\n' 0922 0923 if not isMethodDeclaration: 0924 if shared_class_options['with_from_to_map']: 0925 toMap_impl += ' map[QLatin1String(\"%s\")] = %s;\n' % (member['name'], generate_toString_conversion(member['name'], member['type'])) 0926 fromMap_impl += ' %s\n' % generate_fromString_conversion(member['name'], member['type']) 0927 update_data_accesors() 0928 0929 member = {} 0930 if len(prev_line.split()) == 0: # remove current line because it's not going to the output now; this helps to remove duplicated empty lines 0931 line = '' 0932 after_member = True 0933 elif len(lst) > 0 and lst[0] == '};' and line[:2] == '};' and shared_class_inserted: 0934 insert_generated_code(5) 0935 # write(outfile, '\nprivate:\n'); 0936 write(outfile, '\nprotected:\n'); 0937 if shared_class_options['explicit']: 0938 if superclass: 0939 write(outfile, """ virtual const Data* data() const { return dynamic_cast<const Data*>(%s::d.constData()); } 0940 virtual Data* data() { return dynamic_cast<Data*>(%s::d.data()); } 0941 """ % (superclass, superclass)) 0942 else: 0943 write(outfile, """ %s(Data *data) 0944 : d(data) 0945 { 0946 } 0947 0948 %s(QExplicitlySharedDataPointer<%s::Data> &data) 0949 : d(data) 0950 { 0951 } 0952 0953 QExplicitlySharedDataPointer<Data> d; 0954 """ % (shared_class_name, shared_class_name, shared_class_name)) 0955 else: 0956 write(outfile, ' QSharedDataPointer<Data> d;\n'); 0957 write(outfile, line) 0958 0959 if shared_class_options['explicit']: 0960 write(outfile, """ 0961 template<> 0962 %s %s::Data *QSharedDataPointer<%s::Data>::clone(); 0963 """ % (export_macro, shared_class_name, shared_class_name)) 0964 open_sdc() 0965 write(outfile_sdc, """template<> 0966 %s %s::Data *QSharedDataPointer<%s::Data>::clone() 0967 { 0968 return d->clone(); 0969 } 0970 """ % (export_macro, shared_class_name, shared_class_name)) 0971 0972 else: 0973 #write(outfile, '____ELSE____\n'); 0974 if False and other_comment(line): 0975 prev_pos = infile.tell() 0976 prev_line_number = line_number 0977 ln = line[:-1].strip(' ') 0978 result = '' 0979 print("'" + ln + "'") 0980 if ln.startswith('/**') or ln.startswith('/*!'): 0981 while True: 0982 result += line 0983 if not line or ln.endswith('*/'): 0984 result = result[:-1] 0985 break 0986 prev_line = line 0987 line = infile.readline().decode('utf-8') 0988 line_number += 1 0989 ln = line[:-1].strip(' ') 0990 print(result) 0991 if result: 0992 member['docs'] = result 0993 infile.seek(prev_pos) 0994 prev_line = line 0995 line = infile.readline().decode('utf-8') 0996 lst = line.split() 0997 line_number = prev_line_number 0998 0999 if not lst: 1000 if len(prev_line.split()) > 0: 1001 #write(outfile, '['+prev_line + ']prev_line[' + line +']') 1002 write(outfile, line) 1003 elif not after_member or len(lst) > 0: 1004 if shared_class_inserted: 1005 insert_generated_code(6) 1006 write(outfile, line) 1007 elif generated_code_inserted and len(lst) == 0: 1008 #write(outfile, 'ELSE>>>' + line) 1009 write(outfile, line) 1010 # else: 1011 # write(outfile, line) 1012 1013 process() 1014 1015 # --- close --- 1016 infile.close() 1017 outfile.close()