xed/pysrc/enumer.py
Mark Charney e7d734962c update legal header & date for py3 ported files
Change-Id: I166833daaa56c33eca01bdf7b9aa6e74a490ba9a
(cherry picked from commit 1212ba962dff6dfbfa0bd2469327ff447ce59058)
2017-06-12 14:41:24 -04:00

644 lines
21 KiB
Python
Executable File

#!/usr/bin/env python
# -*- python -*-
# Mark Charney <mark.charney@intel.com>
#BEGIN_LEGAL
#
#Copyright (c) 2017 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
#END_LEGAL
from __future__ import print_function
import os
import sys
import types
import re
import codegen
def find_dir(d):
dir = os.getcwd()
last = ''
while dir != last:
target_dir = os.path.join(dir,d)
#print "Trying %s" % (target_dir)
if os.path.exists(target_dir):
return target_dir
last = dir
(dir,tail) = os.path.split(dir)
return None
sys.path.append(find_dir('mbuild'))
try:
import mbuild
except:
sys.stderr.write("\nERROR(enumer.py): Could not find mbuild. Might try setting PYTHONPATH env var.\n\n")
sys.exit(1)
class enumer_value_t(object):
def __init__(self, name, value=None, doxygen=None, duplicate=False, display_str=None):
self.name = name
# display_str is a string to use for the parser. may contain
# values that are not valid enumeration characters (like
# parenthesis, etc.). display_str was added later and defaults
# to the value supplied for name.
if display_str:
self.display_str = display_str
else:
self.display_str = self.name
self.value=value
self.doxygen=doxygen
self.duplicate=duplicate
def in_comment(self,s):
"""is s substring in comment?"""
if self.doxygen:
if s in self.doxygen:
return True
return False
def _make_str2foo():
l = [
"/// This converts strings to #%(type)s types.",
"/// @param s A C-string.",
"/// @return #%(type)s",
"/// @ingroup ENUM",
"%(prefix)s %(type)s str2%(type)s(const char* s)",
]
s = "\n".join(l)
return s
def _make_foo2str():
l = [
"/// This converts strings to #%(type)s types.",
"/// @param p An enumeration element of type %(type)s.",
"/// @return string",
"/// @ingroup ENUM",
"%(prefix)s const char* %(type)s2str(const %(type)s p)"
]
s = "\n".join(l)
return s
pass
class enumer_t(object):
def __init__(self, type_name, prefix, values, cfn, hfn, gendir,
namespace=None, stream_guard=None,
add_last_element=True, cplusplus=False,
proto_prefix='', extra_header=None, density='automatic',
string_convert=1 ):
"""
@type type_name: string
@param type_name: the name of the generated type
@type prefix: string
@param prefix: prepended to all enumeration names
@type values: list
@param values: list of L{enumer_value_t} objects
@type cfn: string
@param cfn: output source file name
@type hfn: string
@param hfn: output header file name
@type gendir: string
@param gendir: output directory
@type namespace: string
@param namespace: namespace wrapper
@type stream_guard: string
@param stream_guard: #ifdef test for ostream/istream functionality
@type add_last_element: xed_bool_t
@param add_last_element: If True (default), add a _LAST element.
@type cplusplus: xed_bool_t
@param cplusplus: True=>C++ or False=>C
@type proto_prefix: string
@param proto_prefix: default is empty string. useful for DLL export decorations
@type extra_header: string or list
@param extra_header: another header to include in the .H file.
@type density: string
@param density: density of enumerated values. Can be sparse (default) or dense. Default is automatic which use the presence or absence of preset values to determine density
@type string_convert: integer
@param string_convert: 1=default, generate convert routines, 0=empty stubs, -1=no-stubs or prototypes
"""
self.debug = False
self.proto_prefix = proto_prefix
self.cplusplus = cplusplus
self.type_name = type_name
self.prefix = prefix
self.cfn = cfn
self.hfn = hfn
self.density = density
self.string_convert = string_convert
self.extra_header = extra_header
self.values= self._unique(values) # list of enumer_value_t's
self.can_be_dense = True
self.preset_values = self._scan_for_preset_values(self.values)
if self.preset_values:
self.can_be_dense = \
self._scan_for_dense_zero_based_preset_values(self.values)
#sys.stderr.write("Can be dense %s\n" % (str(self.can_be_dense)))
#sys.stderr.write("Preset values %s\n" % (str(self.preset_values)))
if self.density == 'automatic':
if self.can_be_dense:
self.density = 'dense'
else:
self.density = 'sparse'
if self.preset_values and \
self.can_be_dense == False and \
self.density == 'dense':
sys.stderr.write("\nERROR(enumer.py): dense enum had some values specified preventing dense-enum generation\n\n")
sys.exit(1)
self.add_last_element = add_last_element
if add_last_element:
self.values.append(enumer_value_t('LAST'))
(unique, duplicates) = self._partition_duplicates(self.values)
# clobber the values with just the unique values
self.values = unique
self.duplicates = duplicates
self.namespace=namespace
self.stream_guard=stream_guard
self.system_headers = [ "string.h" ]
if self.cplusplus:
self.system_headers.append("cassert")
self.system_headers.append("string")
else:
self.system_headers.append('assert.h')
self.convert_function_headers = [ _make_str2foo(), _make_foo2str() ]
if self.cplusplus:
self.ostream_function_headers = ['std::ostream& operator<<(std::ostream& o, const %s& v)',
'std::istream& operator>>(std::istream& o, %s& v)' ]
self.operator_function_headers = ['%s& operator++(%s& x, int)',
'%s& operator--(%s& x, int)' ]
else:
self.ostream_function_headers = []
self.operator_function_headers = []
if self.cplusplus:
namespace = self.namespace
else:
namespace = None
full_header_file_name = mbuild.join(gendir,self.hfn)
self.hf = codegen.file_emitter_t(gendir, self.hfn,
namespace=namespace)
if type(self.extra_header) == list:
for hdr in self.extra_header:
self.hf.add_header(hdr)
elif self.extra_header:
self.hf.add_header(self.extra_header)
full_source_file_name = mbuild.join(gendir,self.cfn)
self.cf = codegen.file_emitter_t(gendir, self.cfn,
namespace=namespace)
self.cf.add_header(self.hfn)
for sh in self.system_headers:
self.cf.add_system_header(sh)
if self.cplusplus:
if self.stream_guard and self.stream_guard != '':
self.hf.add_misc_header("#if %s==1" % self.stream_guard)
self.hf.add_misc_header("# include <iostream>")
self.hf.add_misc_header("#endif")
else:
self.hf.add_misc_header("#include <iostream>")
self.hf.start()
self.cf.start()
def emit(self):
self._emit_header_file()
self._emit_source_file()
self._close()
#######################################################
def _scan_for_preset_values(self, vals):
for v in vals:
if v.value != None:
return True
return False
def _make_number(self, s):
if re.match(r'^0x',s):
return (True,int(s,16))
if re.match(r'^[0-9]*$',s):
return (True,int(s,10))
return (False,s)
def _partition_duplicates(self, vals):
duplicates = []
unique = []
for v in vals:
if v.duplicate:
duplicates.append(v)
else:
unique.append(v)
return (unique,duplicates)
def _scan_for_dense_zero_based_preset_values(self, vals):
"""Scan the list of values, and check that each one has the
expected zero-based value, or no specified value (as often
happens with the LAST element). Return True if it dense and
zero based. """
if self.debug:
print("SCAN FOR DENSE")
b = 0
n = 0
for v in vals:
if self.debug:
print("\tTESTING [%s]->[%s]" % (v.name, str(v.value)))
if v.value == None:
b = b + 1
n = n + 1
continue
(is_number,ov) = self._make_number(v.value)
if self.debug:
print("\t\t isnum=%s %s" % ( str(is_number), str(ov)))
if is_number and ov == b:
b = b + 1
n = n + 1
continue
# if it matches a previous value then it can still be dense,
# but we must not put this value in the value-2-string
# table. It must be in a separate string2value table.
#print "\t\t [%s]" % ( str(vals[0:n]))
previous_values = [ x.name for x in vals[0:n] ]
if self.debug:
print("\t\t [%s]" % ( str(previous_values)))
if v.value in previous_values:
v.duplicate = True
n = n + 1
continue
if self.debug:
print("\t\t Not in previous values")
return False
return True
def _unique(self, vals):
"""Return a list of unique values, given a list of
enumer_value_t objects"""
uvals = {}
for v in vals:
if v.name in uvals:
if uvals[v.name].value != v.value:
sys.stderr.write("ENUMER ERROR: duplicate key name in enumeration with different values: %s\n" % v.name)
sys.exit(1)
uvals[ v.name ] = v
# attempt to preserve the order of the values in the returned sequence.
# make sure INVALID is first though!
ovals = []
if 'INVALID' in uvals:
ovals.append(uvals['INVALID'])
del uvals['INVALID']
for v in vals:
if v.name in uvals:
ovals.append(uvals[v.name])
del uvals[v.name]
return ovals
def _close(self):
self.cf.close()
self.hf.close()
def _emit_header_file(self):
self._emit_typedef()
if self.string_convert >= 0:
self._emit_convert_protos()
if self.add_last_element:
self._emit_last_fn_proto()
if self.cplusplus:
self._emit_ostream_protos()
self._emit_operators_protos()
def _emit_source_file(self):
if self.string_convert == 1:
self._emit_name_table_type()
self._emit_name_table()
self._emit_duplicate_name_table()
self._emit_converts()
elif self.string_convert == 0:
self._emit_convert_stubs()
if self.add_last_element:
self._emit_last_fn()
if self.cplusplus:
self._emit_ostream()
self._emit_operators()
self._emit_comment()
def _emit_typedef(self):
max = len(self.values) + len(self.duplicates)
self.hf.emit_eol("typedef enum {")
for i,v in enumerate(self.values):
self.hf.emit(" %s%s" % (self.prefix,v.name))
if v.value != None:
self.hf.emit("=%s" % v.value)
if i < max-1:
self.hf.emit(',')
if v.doxygen != None:
self.hf.emit(" %s" % v.doxygen)
self.hf.emit_eol()
if len(self.duplicates) == 0:
self.hf.emit_eol("} %s;" % self.type_name)
self.hf.emit_eol()
return
bias = len(self.values)
for i,v in enumerate(self.duplicates):
self.hf.emit(" %s%s" % (self.prefix,v.name))
if v.value != None:
if v.duplicate:
self.hf.emit("=%s%s" % (self.prefix,v.value))
else:
self.hf.emit("=%s" % v.value)
if i+bias < max-1:
self.hf.emit(',')
if v.doxygen != None:
self.hf.emit(" %s" % v.doxygen)
self.hf.emit_eol()
self.hf.emit_eol("} %s;" % self.type_name)
self.hf.emit_eol()
def _emit_convert_protos(self):
for x in self.convert_function_headers:
d = { 'type' : self.type_name,
'prefix' : self.proto_prefix }
self.hf.add_code_eol(x % (d))
self.hf.emit_eol()
def _emit_ostream_protos(self):
if self.stream_guard and self.stream_guard != '':
self.hf.emit_eol("#if %s==1" % self.stream_guard)
for x in self.ostream_function_headers:
t = "%s " + x
self.hf.add_code_eol(t % (self.proto_prefix,self.type_name))
if self.stream_guard and self.stream_guard != '':
self.hf.emit_eol("#endif")
self.hf.emit_eol()
def _emit_operators_protos(self):
for x in self.operator_function_headers:
t = "%s " + x
self.hf.add_code_eol(t % \
(self.proto_prefix, self.type_name, self.type_name))
self.hf.emit_eol()
def _emit_name_table_type(self):
nt_string = """
typedef struct {
const char* name;
%(type)s value;
} name_table_%(type)s;"""
self.cf.emit_eol(nt_string % {'type':self.type_name})
def _emit_name_table(self):
s = "static const name_table_%(type)s name_array_%(type)s[] = {"
self.cf.emit_eol(s % {'type':self.type_name})
for v in self.values:
s = """{"%s", %s%s},""" % (v.display_str,self.prefix,v.name)
self.cf.emit_eol(s)
s = "{%s, %s%s}," % ('0',self.prefix,self.values[-1].name)
self.cf.emit_eol(s)
self.cf.emit_eol('};')
def _emit_duplicate_name_table(self):
if len(self.duplicates) == 0:
return
s = "static const name_table_%(type)s dup_name_array_%(type)s[] = {"
self.cf.emit_eol(s % {'type':self.type_name})
for v in self.duplicates:
s = """{"%s", %s%s},""" % (v.display_str,self.prefix,v.name)
self.cf.emit_eol(s)
s = "{%s, %s%s}," % ('0',self.prefix,self.values[-1].name)
self.cf.emit_eol(s)
self.cf.emit_eol('};')
def _invalid_or_last(self):
for v in self.values:
if v.name == 'INVALID':
return 'INVALID'
return self.values[-1].name
def _emit_last_fn_proto(self):
"""Emit a function that returns the LAST element"""
l = [
"/// Returns the last element of the enumeration",
"/// @return %(type)s The last element of the enumeration.",
"/// @ingroup ENUM",
"%(proto_prefix)s %(type)s %(type)s_last(void);"
]
s = "\n".join(l)
self.hf.emit_eol(s % {'type':self.type_name,
'proto_prefix':self.proto_prefix} )
def _emit_last_fn(self):
"""Emit a function that returns the LAST element"""
s = """
%(type)s %(type)s_last(void) {
return %(prefix)sLAST;
}
"""
self.cf.emit_eol(s % {'type':self.type_name, 'prefix':self.prefix} )
def _emit_convert_stubs(self):
self._emit_str2enum_convert_stub()
self._emit_enum2str_convert_stub()
def _emit_str2enum_convert_stub(self):
"""Emit a fake from-string converter that always returns invalid"""
top = """
%(type)s str2%(type)s(const char* s)
{
return %(prefix)s%(invalid)s;
(void)s;
}"""
invalid = self._invalid_or_last()
d = {'type':self.type_name,
'prefix':self.prefix,
'invalid':invalid}
self.cf.emit_eol(top % (d))
def _emit_enum2str_convert_stub(self):
"""Emit a fake to-string converter that always returns invalid"""
s = """
const char* %(type)s2str(const %(type)s p)
{
return "INVALID";
(void)p;
}"""
invalid = self._invalid_or_last()
self.cf.emit_eol(s % {'type':self.type_name,
'prefix':self.prefix,
'invalid':invalid})
def _emit_converts(self):
self._emit_str2enum_convert()
self._emit_enum2str_convert()
def _emit_str2enum_convert(self):
top = """
%(type)s str2%(type)s(const char* s)
{
const name_table_%(type)s* p = name_array_%(type)s;
while( p->name ) {
if (strcmp(p->name,s) == 0) {
return p->value;
}
p++;
}
"""
dups = """
{
const name_table_%(type)s* q = dup_name_array_%(type)s;
while( q->name ) {
if (strcmp(q->name,s) == 0) {
return q->value;
}
q++;
}
}
"""
end = """
return %(prefix)s%(invalid)s;
}"""
invalid = self._invalid_or_last()
d = {'type':self.type_name,
'prefix':self.prefix,
'invalid':invalid}
self.cf.emit_eol(top % (d))
if self.duplicates:
self.cf.emit_eol(dups % (d))
self.cf.emit_eol(end % (d))
def _emit_enum2str_convert(self):
if (self.density == 'sparse'):
self._emit_sparse_enum2str_convert()
else:
self._emit_dense_enum2str_convert()
def _emit_sparse_enum2str_convert(self):
s = """
const char* %(type)s2str(const %(type)s p)
{
const name_table_%(type)s* q = name_array_%(type)s;
while( q->name ) {
if (q->value == p) {
return q->name;
}
q++;
}
return "???";
}"""
invalid = self._invalid_or_last()
self.cf.emit_eol(s % {'type':self.type_name,
'prefix':self.prefix,
'invalid':invalid})
def _emit_dense_enum2str_convert(self):
s = """
const char* %(type)s2str(const %(type)s p)
{
%(type)s type_idx = p;
if ( p > %(prefix)sLAST) type_idx = %(prefix)sLAST;
return name_array_%(type)s[type_idx].name;
}"""
invalid = self._invalid_or_last()
self.cf.emit_eol(s % {'type':self.type_name,
'prefix':self.prefix,
'invalid':invalid})
def _emit_ostream(self):
s = """
std::ostream& operator<<(std::ostream& o, const %(type)s& v) {
o << %(type)s2str(v);
return o;
}
std::istream& operator>>(std::istream& i, %(type)s& v) {
std::string s;
i >> s;
v = str2%(type)s( s.c_str() );
return i;
}"""
if self.stream_guard and self.stream_guard != '':
self.cf.emit_eol("#if %s==1" % self.stream_guard)
self.cf.emit_eol(s % {'type':self.type_name})
if self.stream_guard and self.stream_guard != '':
self.cf.emit_eol("#endif")
def _emit_operators(self):
s = """
%(type)s& operator++(%(type)s& x, int)
{
return x = %(type)s(x+1);
}
%(type)s& operator--(%(type)s& x, int)
{
return x = %(type)s(x-1);
}"""
self.cf.emit_eol(s % {'type':self.type_name})
def _emit_comment(self):
self.cf.emit_eol("/*")
self.cf.emit_eol()
self.cf.emit_eol("Here is a skeleton switch statement embedded in a comment")
self.cf.emit_eol()
self.cf.emit_eol()
self.cf.emit_eol(" switch(p) {")
for v in self.values:
self.cf.emit_eol(" case %s%s:" % (self.prefix, v.name))
self.cf.emit_eol(" default:")
self.cf.emit_eol(" xed_assert(0);")
self.cf.emit_eol(" }")
self.cf.emit_eol("*/")
def _test_enumer():
values = map(enumer_value_t, ['aaa','bbb','ccc'])
e = enumer_t("test_type_t", "TEST_TYPE_", values,
"enumer-test.cpp", "enumer-test.H", ".",
namespace = "XED",
stream_guard ="XEDPRINT")
e.emit()
if __name__ == '__main__':
_test_enumer()