glib/gobject/glib-mkenums.in
Tim-Philipp Müller 1672678bc4 glib-mkenums: fix parsing of /*< flags >*/ annotation
Fixes get_type function generation for:

 - GMountMountFlags
 - GDriveStartFlags
 - GResourceLookupFlags
 - GSocketMsgFlags
 - GTlsDatabaseVerifyFlags
 - GTestDBusFlags

which were registered as enum types before, which broke
some unit tests.

Problem is that the flags annotation has no value, so
options.get('flags') would always return None even if
it was present.

https://bugzilla.gnome.org/show_bug.cgi?id=779332
2017-07-13 19:03:39 -04:00

594 lines
22 KiB
Python
Executable File

#!/usr/bin/env python3
# If the code below looks horrible and unpythonic, do not panic.
#
# It is.
#
# This is a manual conversion from the original Perl script to
# Python. Improvements are welcome.
import os, sys, re, argparse, tempfile
output_stream = sys.stdout
def write_output(output):
global output_stream
print(output, file=output_stream)
version = '@GLIB_VERSION@'
# glib-mkenums.py
# Information about the current enumeration
flags = False # Is enumeration a bitmask?
option_underscore_name = '' # Overriden underscore variant of the enum name
# for example to fix the cases we don't get the
# mixed-case -> underscorized transform right.
option_lowercase_name = '' # DEPRECATED. A lower case name to use as part
# of the *_get_type() function, instead of the
# one that we guess. For instance, when an enum
# uses abnormal capitalization and we can not
# guess where to put the underscores.
seenbitshift = 0 # Have we seen bitshift operators?
enum_prefix = None # Prefix for this enumeration
enumname = '' # Name for this enumeration
enumshort = '' # $enumname without prefix
enumname_prefix = '' # prefix of $enumname
enumindex = 0 # Global enum counter
firstenum = 1 # Is this the first enumeration per file?
entries = [] # [ name, val ] for each entry
sandbox = None # sandbox for safe evaluation of expressions
output = '' # Filename to write result into
def parse_trigraph(opts):
result = {}
for opt in re.split(r'\s*,\s*', opts):
opt = re.sub(r'^\s*', '', opt)
opt = re.sub(r'\s*$', '', opt)
m = re.search(r'(\w+)(?:=(.+))?', opt)
assert(m is not None)
groups = m.groups()
key = groups[0]
if len(groups) > 1:
val = groups[1]
else:
val = 1
result[key] = val
return result
def parse_entries(file, file_name):
global entries, enumindex, enumname, seenbitshift, flags
looking_for_name = False
for line in file:
# read lines until we have no open comments
while re.search(r'/\*([^*]|\*(?!/))*$', line):
line = file.readline()
# strip comments w/o options
line = re.sub(r'''/\*(?!<)
([^*]+|\*(?!/))*
\*/''', '', line, flags=re.X)
line = line.rstrip()
# skip empty lines
if len(line.strip()) == 0:
continue
if looking_for_name:
m = re.match('\s*(\w+)', line)
if m:
enumname = m.group(1);
return True
# Handle include files
m = re.match(r'\#include\s*<([^>]*)>', line)
if m:
newfilename = os.path.join("..", m.group(1))
newfile = open(newfilename)
if not parse_entries(newfile, newfilename):
return False
else:
continue
m = re.match(r'\s*\}\s*(\w+)', line)
if m:
enumname = m.group(1)
enumindex+=1;
return 1;
m = re.match(r'\s*\}', line)
if m:
enumindex+=1;
looking_for_name = True
continue
m = re.match(r'''\s*
(\w+)\s* # name
(?:=( # value
\s*\w+\s*\(.*\)\s* # macro with multiple args
| # OR
(?:[^,/]|/(?!\*))* # anything but a comma or comment
))?,?\s*
(?:/\*< # options
(([^*]|\*(?!/))*)
>\s*\*/)?,?
\s*$''', line, flags=re.X)
if m:
groups = m.groups()
name = groups[0]
value = None
options = None
if len(groups) > 1:
value = groups[1]
if len(groups) > 2:
options = groups[2]
if not flags and value is not None and '<<' in value:
seenbitshift = 1
if options is not None:
options = parse_trigraph(options)
if 'skip' in options:
entries.append((name, value, options['nick']));
else:
entries.append((name, value))
elif re.match('s*\#', line):
pass
else:
sys.exit("Failed to parse %s." % file_name)
return False
def print_version():
print("glib-mkenums version glib-" + version)
print("glib-mkenums comes with ABSOLUTELY NO WARRANTY.")
print("You may redistribute copies of glib-mkenums under the terms of")
print("the GNU General Public License which can be found in the")
print("GLib source package. Sources, examples and contact")
print("information are available at http://www.gtk.org")
sys.exit(0)
help_epilog = '''Production text substitutions:
\u0040EnumName\u0040 PrefixTheXEnum
\u0040enum_name\u0040 prefix_the_xenum
\u0040ENUMNAME\u0040 PREFIX_THE_XENUM
\u0040ENUMSHORT\u0040 THE_XENUM
\u0040ENUMPREFIX\u0040 PREFIX
\u0040VALUENAME\u0040 PREFIX_THE_XVALUE
\u0040valuenick\u0040 the-xvalue
\u0040valuenum\u0040 the integer value (limited support, Since: 2.26)
\u0040type\u0040 either enum or flags
\u0040Type\u0040 either Enum or Flags
\u0040TYPE\u0040 either ENUM or FLAGS
\u0040filename\u0040 name of current input file
\u0040basename\u0040 base name of the current input file (Since: 2.22)
'''
# production variables:
idprefix = "" # "G", "Gtk", etc
symprefix = "" # "g", "gtk", etc, if not just lc($idprefix)
fhead = "" # output file header
fprod = "" # per input file production
ftail = "" # output file trailer
eprod = "" # per enum text (produced prior to value itarations)
vhead = "" # value header, produced before iterating over enum values
vprod = "" # value text, produced for each enum value
vtail = "" # value tail, produced after iterating over enum values
comment_tmpl = "" # comment template
def read_template_file(file):
global idprefix, symprefix, fhead, fprod, ftail, eprod, vhead, vprod, vtail, comment_tmpl
tmpl = {'file-header': fhead,
'file-production': fprod,
'file-tail': ftail,
'enumeration-production': eprod,
'value-header': vhead,
'value-production': vprod,
'value-tail': vtail,
'comment': comment_tmpl,
}
in_ = 'junk'
ifile = open(file)
for line in ifile:
m = re.match(r'\/\*\*\*\s+(BEGIN|END)\s+([\w-]+)\s+\*\*\*\/', line)
if m:
if in_ == 'junk' and m.group(1) == 'BEGIN' and m.group(2) in tmpl:
in_ = m.group(2)
continue
elif in_ == m.group(2) and m.group(1) == 'END' and m.group(2) in tmpl:
in_ = 'junk'
continue
else:
sys.exit("Malformed template file " + file);
if in_ != 'junk':
tmpl[in_] += line
if in_ != 'junk':
sys.exit("Malformed template file " + file)
fhead = tmpl['file-header']
fprod = tmpl['file-production']
ftail = tmpl['file-tail']
eprod = tmpl['enumeration-production']
vhead = tmpl['value-header']
vprod = tmpl['value-production']
vtail = tmpl['value-tail']
comment_tmpl = tmpl['comment']
# default to C-style comments
if comment_tmpl == "":
comment_tmpl = "/* \u0040comment\u0040 */"
if len(sys.argv) == 1:
usage()
parser = argparse.ArgumentParser(epilog=help_epilog,
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument('--identifier-prefix', default='', dest='idprefix',
help='Identifier prefix')
parser.add_argument('--symbol-prefix', default='', dest='symprefix',
help='symbol-prefix')
parser.add_argument('--fhead', default='', dest='fhead',
help='Output file header')
parser.add_argument('--ftail', default='', dest='ftail',
help='Per input file production')
parser.add_argument('--eprod', default='', dest='eprod',
help='Per enum text (produced prior to value iterations)')
parser.add_argument('--vhead', default='', dest='vhead',
help='Value header, produced before iterating over enum values')
parser.add_argument('--vprod', default='', dest='vprod',
help='Value text, produced for each enum value.')
parser.add_argument('--vtail', default='', dest='vtail',
help='Value tail, produced after iterating over enum values')
parser.add_argument('--comments', default='', dest='comment_tmpl',
help='Comment structure')
parser.add_argument('--template', default='', dest='template',
help='Template file')
parser.add_argument('--output', default=None, dest='output')
parser.add_argument('--version', '-v', default=False, action='store_true', dest='version',
help='Print version informations')
parser.add_argument('args', nargs='*')
options = parser.parse_args()
if options.version:
print_version()
if options.template != '':
read_template_file(options.template)
idprefix += options.idprefix
symprefix += options.symprefix
fhead += options.fhead
ftail += options.ftail
eprod += options.eprod
vhead += options.vhead
vprod += options.vprod
vtail += options.vtail
if options.comment_tmpl != '':
comment_tmpl = options.comment_tmpl
output = options.output
if output is not None:
(out_dir, out_fn) = os.path.split(options.output)
out_suffix = '_' + os.path.splitext(out_fn)[1]
if out_dir == '':
out_dir = '.'
tmpfile = tempfile.NamedTemporaryFile(dir=out_dir, delete=False)
output_stream = tmpfile
else:
tmpfile = None
# put auto-generation comment
comment = comment_tmpl.replace('\u0040comment\u0040', 'Generated data (by glib-mkenums)')
write_output("\n" + comment + '\n')
def replace_specials(prod):
prod = prod.replace(r'\\a', r'\a')
prod = prod.replace(r'\\b', r'\b')
prod = prod.replace(r'\\t', r'\t')
prod = prod.replace(r'\\n', r'\n')
prod = prod.replace(r'\\f', r'\f')
prod = prod.replace(r'\\r', r'\r')
prod = prod.rstrip()
return prod
if len(fhead) > 0:
prod = fhead;
base = os.path.basename(options.args[0])
prod = prod.replace('\u0040filename\u0040', options.args[0])
prod = prod.replace('\u0040basename\u0040', base)
prod = replace_specials(prod)
write_output(prod)
def process_file(curfilename):
global entries, flags, seenbitshift, enum_prefix
firstenum = True
curfile = open(curfilename)
for line in curfile:
# read lines until we have no open comments
while re.search(r'/\*([^*]|\*(?!/))*$', line):
line = curfile.readline()
# strip comments w/o options
line = re.sub(r'''/\*(?!<)
([^*]+|\*(?!/))*
\*/''', '', line)
# ignore forward declarations
if re.match(r'\s*typedef\s+enum.*;', line):
continue
m = re.match(r'''\s*typedef\s+enum\s*
({)?\s*
(?:/\*<
(([^*]|\*(?!/))*)
>\s*\*/)?
\s*({)?''', line, flags=re.X)
if m:
groups = m.groups()
if len(groups) >= 2 and groups[1] is not None:
options = parse_trigraph(groups[1]);
if 'skip' in options:
continue
enum_prefix = options.get('prefix', None)
flags = 'flags' in options
option_lowercase_name = options.get('lowercase_name', None)
option_underscore_name = options.get('underscore_name', None)
else:
enum_prefix = None
flags = False
option_lowercase_name = None
option_underscore_name = None
if option_lowercase_name is not None:
if option_underscore_name is not None:
print("$0: $ARGV:$.: lowercase_name overriden with underscore_name", file=sys.stderr)
option_lowercase_name = None
else:
print("$0: $ARGV:$.: lowercase_name is deprecated, use underscore_name", file=sys.stderr)
# Didn't have trailing '{' look on next lines
if groups[0] is None and (len(groups) < 4 or groups[3] is None):
while True:
line = curfile.readline()
if re.match(r'\s*\{', line):
break
seenbitshift = 0;
entries = [];
# Now parse the entries
parse_entries(curfile, curfilename);
# figure out if this was a flags or enums enumeration
if not flags:
flags = seenbitshift
# Autogenerate a prefix
if enum_prefix is None:
for entry in entries:
if len(entry) < 3 or entry[2] is None:
name = entry[0];
if enum_prefix is not None:
enum_prefix = os.path.commonprefix([name, enum_prefix])
else:
enum_prefix = name;
if enum_prefix is None:
enum_prefix = ""
else:
# Trim so that it ends in an underscore
enum_prefix = re.sub(r'_[^_]*$', '_', enum_prefix)
else:
# canonicalize user defined prefixes
enum_prefix = enum_prefix.upper()
enum_prefix = enum_prefix.replace('-', '_')
enum_prefix = re.sub(r'(.*)([^_])$', r'\1\2_')
fixed_entries = []
for e in entries:
name = e[0]
num = e[1]
if len(e) < 3 or e[2] is None:
nick = re.sub(r'^' + enum_prefix, '', name)
nick = nick.replace('_', '-').lower()
e = (name, num, nick)
fixed_entries.append(e)
entries = fixed_entries
# Spit out the output
if option_underscore_name is not None:
enumlong = option_underscore_name.upper()
enumsym = option_underscore_name.lower()
enumshort = re.sub(r'^[A-Z][A-Z0-9]*_', '', enumlong)
enumname_prefix = re.sub('_' + enumshort + '$', '', enumlong)
elif symprefix == '' and idprefix == '':
# enumname is e.g. GMatchType
enspace = re.sub(r'^([A-Z][a-z]*).*$', r'\1', enumname)
enumshort = re.sub(r'^[A-Z][a-z]*', '', enumname)
enumshort = re.sub(r'([^A-Z])([A-Z])', r'\1_\2', enumshort)
enumshort = re.sub(r'([A-Z][A-Z])([A-Z][0-9a-z])', r'\1_\2', enumshort)
enumshort = enumshort.upper()
enumname_prefix = re.sub(r'^([A-Z][a-z]*).*$', r'\1', enumname).upper()
enumlong = enspace.upper() + "_" + enumshort
enumsym = enspace.lower() + "_" + enumshort.lower()
if option_lowercase_name is not None:
enumsym = option_lowercase_name;
else:
enumshort = enumname;
if idprefix:
enumshort = re.sub(r'^' + idprefix, '', enumshort)
else:
enumshort = re.sub(r'/^[A-Z][a-z]*', '', enumshort)
enumshort = re.sub(r'([^A-Z])([A-Z])', r'\1_\2', enumshort)
enumshort = re.sub(r'([A-Z][A-Z])([A-Z][0-9a-z])', r'\1_\2', enumshort)
enumshort = enumshort.upper();
if symprefix is not None:
enumname_prefix = symprefix.upper()
else:
enumname_prefix = idprefix.upper()
enumlong = enumname_prefix + "_" + enumshort
enumsym = enumlong.lower()
if firstenum:
firstenum = False
if len(fprod) > 0:
prod = fprod;
base = os.path.basename(curfilename);
prod = prod.replace('\u0040filename\u0040', curfilename)
prod = prod.replace('\u0040basename\u0040', base)
prod = replace_specials(prod)
write_output(prod)
if len(eprod) > 0:
prod = eprod;
prod = prod.replace('\u0040enum_name\u0040', enumsym)
prod = prod.replace('\u0040EnumName\u0040', enumname)
prod = prod.replace('\u0040ENUMSHORT\u0040', enumshort)
prod = prod.replace('\u0040ENUMNAME\u0040', enumlong)
prod = prod.replace('\u0040ENUMPREFIX\u0040', enumname_prefix)
if flags:
prod =~ prod.replace('\u0040type\u0040', 'flags')
else:
prod = prod.replace('\u0040type\u0040', 'enum')
if flags:
prod = prod.replace('\u0040Type\u0040', 'Flags')
else:
prod = prod.replace('\u0040Type\u0040', 'Enum')
if flags:
prod = prod.replace('\u0040TYPE\u0040', 'FLAGS')
else:
prod = prod.replace('\u0040TYPE\u0040', 'ENUM')
prod = replace_specials(prod)
write_output(prod)
if len(vhead) > 0:
prod = vhead;
prod = prod.replace('\u0040enum_name\u0040', enumsym)
prod = prod.replace('\u0040EnumName\u0040', enumname)
prod = prod.replace('\u0040ENUMSHORT\u0040', enumshort)
prod = prod.replace('\u0040ENUMNAME\u0040', enumlong)
prod = prod.replace('\u0040ENUMPREFIX\u0040', enumname_prefix)
if flags:
prod = prod.replace('\u0040type\u0040', 'flags')
else:
prod = prod.replace('\u0040type\u0040', 'enum')
if flags:
prod = prod.replace('\u0040Type\u0040', 'Flags')
else:
prod = prod.replace('\u0040Type\u0040', 'Enum')
if flags:
prod = prod.replace('\u0040TYPE\u0040', 'FLAGS')
else:
prod = prod.replace('\u0040TYPE\u0040', 'ENUM')
prod = replace_specials(prod)
write_output(prod)
if len(vprod) > 0:
prod = vprod;
next_num = 0
prod = replace_specials(prod)
for name, num, nick in entries:
tmp_prod = prod
if '\u0040valuenum\u0040' in prod:
# only attempt to eval the value if it is requested
# this prevents us from throwing errors otherwise
if num is not None:
# use sandboxed evaluation as a reasonable
# approximation to C constant folding
inum = eval(num, {}, {});
# make sure it parsed to an integer
if not isinstance(inum, int):
sys.exit("Unable to parse enum value '%s'" % num)
num = inum
else:
num = next_num
tmp_prod = tmp_prod.replace('\u0040valuenum\u0040', str(num))
next_num = int(num) + 1
tmp_prod = tmp_prod.replace('\u0040VALUENAME\u0040', name)
tmp_prod = tmp_prod.replace('\u0040valuenick\u0040', nick)
if flags:
tmp_prod = tmp_prod.replace('\u0040type\u0040', 'flags')
else:
tmp_prod = tmp_prod.replace('\u0040type\u0040', 'enum')
if flags:
tmp_prod = tmp_prod.replace('\u0040Type\u0040', 'Flags')
else:
tmp_prod = tmp_prod.replace('\u0040Type\u0040', 'Enum')
if flags:
tmp_prod = tmp_prod.replace('\u0040TYPE\u0040', 'FLAGS')
else:
tmp_prod = tmp_prod.replace('\u0040TYPE\u0040', 'ENUM')
tmp_prod = tmp_prod.rstrip()
write_output(tmp_prod)
if len(vtail) > 0:
prod = vtail
prod = prod.replace('\u0040enum_name\u0040', enumsym)
prod = prod.replace('\u0040EnumName\u0040', enumname)
prod = prod.replace('\u0040ENUMSHORT\u0040', enumshort)
prod = prod.replace('\u0040ENUMNAME\u0040', enumlong)
prod = prod.replace('\u0040ENUMPREFIX\u0040', enumname_prefix)
if flags:
prod = prod.replace('\u0040type\u0040', 'flags')
else:
prod = prod.replace('\u0040type\u0040', 'enum')
if flags:
prod = prod.replace('\u0040Type\u0040', 'Flags')
else:
prod = prod.replace('\u0040Type\u0040', 'Enum')
if flags:
prod = prod.replace('\u0040TYPE\u0040', 'FLAGS')
else:
prod = prod.replace('\u0040TYPE\u0040', 'ENUM')
prod = replace_specials(prod)
write_output(prod)
for fname in options.args:
process_file(fname)
if len(ftail) > 0:
prod = ftail
base = os.path.basename(options.args[-1]) # FIXME, wrong
prod = prod.replace('\u0040filename\u0040', 'ARGV') # wrong too
prod = prod.replace('\u0040basename\u0040', base)
prod = replace_specials(prod)
write_output(prod)
# put auto-generation comment
comment = comment_tmpl;
comment = comment.replace('\u0040comment\u0040', 'Generated data ends here')
write_output("\n" + comment + "\n")
if tmpfile is not None:
tmpfilename = tmpfile.name
tmpfile.close()
os.unlink(options.output)
os.rename(tmpfilename, options.output)