File indexing completed on 2024-04-21 15:30:22

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()