From b566e4bcd0c7cb3c90ad941b37db223134090ded Mon Sep 17 00:00:00 2001 From: Nicolas Schodet Date: Fri, 8 May 2009 23:07:25 +0200 Subject: * tools/dfagen: - added state attributes. - added support for more than one initial state. - added table of transitions with only one default branch. - added state and event template parameter. - added --output-dir. - make template directory relative to config file. - more options checking. - added transition attributes and callback definition. - conserve input file order in output. - added --dump and more option checking. - fixed missing newline. --- tools/dfagen/dfagen/automaton.py | 72 ++++++++--- tools/dfagen/dfagen/command.py | 16 ++- tools/dfagen/dfagen/output/__init__.py | 1 + tools/dfagen/dfagen/output/c.py | 160 +++++++++++++++++++----- tools/dfagen/dfagen/output/c/template.c | 2 +- tools/dfagen/dfagen/output/c/template_cb_decl.h | 2 +- tools/dfagen/dfagen/output/c/template_cb_impl.c | 2 +- tools/dfagen/dfagen/output/dot.py | 11 +- tools/dfagen/dfagen/parser.g | 16 ++- tools/dfagen/examples/ex1_cb.c.patch | 2 +- tools/dfagen/examples/ex2.fsm | 8 +- tools/dfagen/examples/ex2_cb.c.patch | 50 ++++---- tools/dfagen/examples/tpl/template_defs.h | 9 +- tools/dfagen/examples/tpl/template_table.h | 16 ++- 14 files changed, 273 insertions(+), 94 deletions(-) diff --git a/tools/dfagen/dfagen/automaton.py b/tools/dfagen/dfagen/automaton.py index 05dda350..e0239df7 100644 --- a/tools/dfagen/dfagen/automaton.py +++ b/tools/dfagen/dfagen/automaton.py @@ -15,28 +15,40 @@ class Event: class State: """State definition.""" - def __init__ (self, name, comments = ''): + def __init__ (self, name, comments = '', initial = False): self.name = name self.comments = comments + self.initial = initial self.transitions = { } + self.transitions_list = [ ] + self.attributes = None def __str__ (self): - s = ' ' + self.name + '\n' + s = ' ' + self.name + if self.attributes: + s += ' [ %s ]' % self.attributes + s += '\n' if self.comments: s += ' ' + self.comments.replace ('\n', '\n ') + '\n' return s def add_branch (self, branch): if branch.event not in self.transitions: - self.transitions[branch.event] = Transition (branch.event) + tr = Transition (branch.event) + self.transitions[branch.event] = tr + self.transitions_list.append (tr) self.transitions[branch.event].add_branch (branch) + def iter_transitions (self): + return iter (self.transitions_list) + class Transition: """Transition definition.""" def __init__ (self, event): self.event = event self.branches = { } + self.branches_list = [ ] def add_branch (self, branch): assert self.event is branch.event @@ -47,12 +59,17 @@ class Transition: if branch.name in self.branches: raise KeyError (branch.name) self.branches[branch.name] = branch + self.branches_list.append (branch) + + def iter_branches (self): + return iter (self.branches_list) + + def get_attributes (self): + return [ b.attributes for b in self.iter_branches () + if b.attributes is not None ] def __str__ (self): - s = '' - for br in self.branches.values (): - s += str (br); - return s + return ''.join (str (br) for br in self.iter_branches ()) class TransitionBranch: @@ -61,51 +78,68 @@ class TransitionBranch: self.name = name self.to = to self.comments = comments + self.attributes = None def __str__ (self): s = ' ' + self.event.name if self.name: s += ': ' + self.name - s += ' -> ' + (self.to and self.to.name or '.') + '\n' + s += ' -> ' + (self.to and self.to.name or '.') + if self.attributes: + s += ' [ %s ]' % self.attributes + s += '\n' if self.comments: s += ' ' + self.comments.replace ('\n', '\n ') + '\n' return s class Automaton: - + def __init__ (self, name): self.name = name self.comments = '' - self.initial = None + self.initials = [ ] self.states = { } + self.states_list = [ ] self.events = { } + self.events_list = [ ] def add_state (self, state): if state.name in self.states: raise KeyError (state.name) + if state.initial: + self.initials.append (state) if not self.states: - self.initial = state + state.initial = True + self.initials.append (state) self.states[state.name] = state + self.states_list.append (state) def add_event (self, event): if event.name in self.events: raise KeyError (event.name) self.events[event.name] = event + self.events_list.append (event) + + def iter_states (self): + return iter (self.states_list) + + def iter_initials (self): + return iter (self.initials) + + def iter_events (self): + return iter (self.events_list) def __str__ (self): - s = self.name + s = self.name + '\n' if self.comments: s += ' ' + self.comments.replace ('\n', '\n ') + '\n' s += '\nStates:\n' - for state in self.states.values (): - s += str (state) + s += ''.join (str (state) for state in self.iter_states ()) s += '\nEvents:\n' - for event in self.events.values (): - s += str (event) + s += ''.join (str (state) for state in self.iter_events ()) s += '\n' - for state in self.states.values (): + for state in self.iter_states (): s += state.name + ':\n' - for tr in state.transitions.values (): - s += str (tr) + s += ''.join (str (tr) for tr in state.iter_transitions ()) s += '\n' return s diff --git a/tools/dfagen/dfagen/command.py b/tools/dfagen/dfagen/command.py index 8a976023..3325d6c1 100644 --- a/tools/dfagen/dfagen/command.py +++ b/tools/dfagen/dfagen/command.py @@ -17,13 +17,25 @@ def run (): help='read output configuration from FILE', metavar='FILE') opt.add_option ('-p', '--prefix', dest='prefix', help='use PREFIX for generated output', metavar='PREFIX') - # TODO add more error checking. + opt.add_option ('-O', '--output-dir', dest='output_dir', default='', + help='generate output in DIR', metavar='DIR') + opt.add_option ('--dump', action='store_true', default=False, + help='dump the read automaton') (options, args) = opt.parse_args () + if (options.dfa is None + or not options.dump and options.output is None + or len (args) != 0): + opt.error ('bad arguments') # Read automaton. f = open (options.dfa, 'r') a = dfagen.parser.parse ('automaton', f.read ()) f.close () + # Dump automaton. + if options.dump: + print a # Read config. cfg = dfagen.output.UserConfig (options.config) # Produce output. - dfagen.output.get_output (options.output).write (options.prefix, a, cfg) + if options.output is not None: + dfagen.output.get_output (options.output).write (options.prefix, a, + cfg, options.output_dir) diff --git a/tools/dfagen/dfagen/output/__init__.py b/tools/dfagen/dfagen/output/__init__.py index 567ab745..eadd8c2f 100644 --- a/tools/dfagen/dfagen/output/__init__.py +++ b/tools/dfagen/dfagen/output/__init__.py @@ -2,6 +2,7 @@ from ConfigParser import ConfigParser class UserConfig: def __init__ (self, file): + self.file = file if file: f = open (file, 'r') cp = ConfigParser () diff --git a/tools/dfagen/dfagen/output/c.py b/tools/dfagen/dfagen/output/c.py index 099302f9..5a44d0e3 100644 --- a/tools/dfagen/dfagen/output/c.py +++ b/tools/dfagen/dfagen/output/c.py @@ -1,48 +1,108 @@ import os.path +import re + +class StateData: + """Data associated with a state when enumerating, used with templates.""" + + def __init__ (self, state, prefix): + self.state = state + self.dict = dict ( + state = state.name, + prefix = prefix, + PREFIX = prefix.upper (), + ) + + def __getitem__ (self, key): + # Second argument may be a default value. + key = key.split ('|', 1) + [ '' ] + key, default = key[0], key[1] + val = None + # Get value. + if key in self.dict: + val = self.dict[key] + elif key == '@': + val = self.state.attributes + elif key.startswith ('@'): + if self.state.attributes is not None: + a = dict (p.split ('=') + for p in self.state.attributes.split ()) + try: + val = a[key[1:]] + except KeyError: + pass + else: + raise KeyError, key + # Test for empty value, and return. + if val is None: + val = default + return val + class WriterData: + callback_re = re.compile ('^\w+$') + def __init__ (self, prefix, automaton, user): self.prefix = prefix self.automaton = automaton self.user = user - self.states = self.automaton.states.values () - self.events = self.automaton.events.values () + self.states = self.automaton.iter_states + self.initials = self.automaton.iter_initials + self.events = self.automaton.iter_events self.dict = dict ( prefix = prefix, PREFIX = prefix.upper (), name = automaton.name, comments = automaton.comments, - initial = automaton.initial.name, states = self.list_states, + initials = self.list_initials, events = self.list_events, states_names = self.list_states_names, events_names = self.list_events_names, + initials_nb = str (len (automaton.initials)), branches = self.list_branches, + only_branch_table = self.only_branch_table, transition_table = self.transition_table, states_template = self.states_template, ) + if len (automaton.initials) == 1: + self.dict['initial'] = automaton.initials[0].name + + def list_states_sub (self, iter, template = None): + if template is None: + template = '%(PREFIX)s_STATE_%(state)s' + return ''.join (' ' + + template % StateData (s, self.prefix) + + ',\n' for s in iter ()) + + def list_states (self, template = None): + return self.list_states_sub (self.states, template) - def list_states (self): - return ''.join ([' ' + self.prefix.upper () + '_STATE_' + s.name - + ',\n' for s in self.states]) + def list_initials (self, template = None): + return self.list_states_sub (self.initials, template) - def list_events (self): - return ''.join ([' ' + self.prefix.upper () + '_EVENT_' - + e.name.replace (' ', '_') + ',\n' for e in self.events]) + def list_events (self, template = None): + if template is None: + template = '%(PREFIX)s_EVENT_%(event)s' + return ''.join (' ' + + template % dict ( + PREFIX = self.prefix.upper (), + event = e.name.replace (' ', '_') + ) + + ',\n' for e in self.events ()) def list_states_names (self): - return ''.join ([' "' + s.name + '",\n' for s in self.states]) + return ''.join (' "' + s.name + '",\n' for s in self.states ()) def list_events_names (self): - return ''.join ([' "' + e.name.replace (' ', '_') + '",\n' - for e in self.events]) + return ''.join (' "' + e.name.replace (' ', '_') + '",\n' + for e in self.events ()) def list_branches (self): l = '' - for s in self.states: - for tr in s.transitions.values (): - for br in tr.branches.values (): + for s in self.states (): + for tr in s.iter_transitions (): + for br in tr.iter_branches (): n = dict ( PREFIX = self.prefix.upper (), state = s.name, @@ -55,15 +115,54 @@ class WriterData: + '_BRANCH (%(state)s, %(event)s, %(to)s),\n') % n return l + def only_branch_table (self, template = None, null = None): + if template is None: + template = '%(PREFIX)s_STATE_%(state)s' + if null is None: + null = template + r = '' + for s in self.states (): + r += ' { ' + es = [ ] + for e in self.events (): + to = None + t = None + if e in s.transitions and None in s.transitions[e].branches: + br = s.transitions[e].branches[None] + to = br.to and br.to.name or s.name + t = template + else: + to = 'NB' + t = null + es.append (t % dict ( + PREFIX = self.prefix.upper (), + state = to, + )) + r += ',\n '.join (es) + r += ' },\n' + return r + + def transition_callback (self, state, event): + attr = state.transitions[event].get_attributes () + if attr: + assert len (attr) == 1, ("multiple callbacks for transition on " + + "event %s for state %s" % (event.name, state.name)) + callback = attr[0] + assert self.callback_re.match (callback), ("bad callback name %s" + % callback) + return callback + else: + return (self.prefix + '__' + state.name + '__' + + event.name.replace (' ', '_')) + def transition_table (self): r = '' - for s in self.states: + for s in self.states (): r += ' { ' es = [ ] - for e in self.events: + for e in self.events (): if e in s.transitions: - es.append (self.prefix + '__' + s.name + '__' - + e.name.replace (' ', '_')) + es.append (self.transition_callback (s, e)) else: es.append ('NULL') r += ',\n '.join (es) @@ -75,8 +174,8 @@ class WriterData: tt = t.read () t.close () exp = '' - for s in self.states: - for tr in s.transitions.values (): + for s in self.states (): + for tr in s.iter_transitions (): d = WriterData (self.prefix, self.automaton, self.user) branches_to = '\n'.join ( [(br.name and br.name or '') @@ -84,7 +183,7 @@ class WriterData: + (br.to and br.to.name or s.name) + (br.comments and ('\n ' + br.comments.replace ('\n', '\n ')) or '') - for br in tr.branches.values ()]) + for br in tr.iter_branches ()]) returns = '\n'.join ( [' return ' + self.prefix + '_next' + (br.name and '_branch' or '') @@ -93,7 +192,7 @@ class WriterData: + (br.name and ', ' + br.name.replace (' ', '_') or '') + ');' - for br in tr.branches.values ()]) + for br in tr.iter_branches ()]) d.dict = dict ( prefix = self.prefix, user = self.user, @@ -101,6 +200,7 @@ class WriterData: event = tr.event.name.replace (' ', '_'), branches_to = branches_to, returns = returns, + callback = self.transition_callback (s, tr.event), ) exp += tt % d return exp @@ -131,17 +231,18 @@ class WriterData: class Writer: - def __init__ (self, data, templatedir): + def __init__ (self, data, templatedir, outputdir): data.templatedir = templatedir self.data = data self.templatedir = templatedir + self.outputdir = outputdir def write_template (self, template, output): t = open (os.path.join (self.templatedir, template), 'r') tt = t.read () t.close () exp = tt % self.data - o = open (output, 'w') + o = open (os.path.join (self.outputdir, output), 'w') o.write (exp) o.close () @@ -157,8 +258,11 @@ class Writer: for (t, f) in templates.iteritems (): self.write_template (t, f.replace ('%', self.data.prefix)) -def write (prefix, automaton, user): - w = Writer (WriterData (prefix, automaton, user), 'template-dir' in user - and user['template-dir'] or os.path.splitext (__file__)[0]) +def write (prefix, automaton, user, outputdir): + templatedir = os.path.splitext (__file__)[0] + if 'template-dir' in user: + templatedir = os.path.join (os.path.split (user.file)[0], + user['template-dir']) + w = Writer (WriterData (prefix, automaton, user), templatedir, outputdir) w.write () diff --git a/tools/dfagen/dfagen/output/c/template.c b/tools/dfagen/dfagen/output/c/template.c index a8679046..6beda928 100644 --- a/tools/dfagen/dfagen/output/c/template.c +++ b/tools/dfagen/dfagen/output/c/template.c @@ -8,7 +8,7 @@ #include #include -%(_user.type-decl)s +%(_user.type-decl)s /* %(name)s transition table. */ static const %(prefix)s_transition_t %(prefix)s_transition_table[%(PREFIX)s_STATE_NB][%(PREFIX)s_EVENT_NB] = { diff --git a/tools/dfagen/dfagen/output/c/template_cb_decl.h b/tools/dfagen/dfagen/output/c/template_cb_decl.h index a1e7c0f2..3bb0a35f 100644 --- a/tools/dfagen/dfagen/output/c/template_cb_decl.h +++ b/tools/dfagen/dfagen/output/c/template_cb_decl.h @@ -2,5 +2,5 @@ * %(state)s =%(event)s=> %(*branches_to)s */ %(prefix)s_branch_t -%(prefix)s__%(state)s__%(event)s (%(user.type)s *user); +%(callback)s (%(user.type)s *user); diff --git a/tools/dfagen/dfagen/output/c/template_cb_impl.c b/tools/dfagen/dfagen/output/c/template_cb_impl.c index ffd9720f..e5a4d789 100644 --- a/tools/dfagen/dfagen/output/c/template_cb_impl.c +++ b/tools/dfagen/dfagen/output/c/template_cb_impl.c @@ -2,7 +2,7 @@ * %(state)s =%(event)s=> %(*branches_to)s */ %(prefix)s_branch_t -%(prefix)s__%(state)s__%(event)s (%(user.type)s *user) +%(callback)s (%(user.type)s *user) { %(returns)s } diff --git a/tools/dfagen/dfagen/output/dot.py b/tools/dfagen/dfagen/output/dot.py index d6cc057c..ce9c4db8 100644 --- a/tools/dfagen/dfagen/output/dot.py +++ b/tools/dfagen/dfagen/output/dot.py @@ -1,12 +1,13 @@ +import os.path -def write (prefix, automaton, user): +def write (prefix, automaton, user, outputdir): output = prefix + '.dot' - o = open (output, 'w') + o = open (os.path.join (outputdir, output), 'w') o.write ('digraph %s {' % prefix) - for s in automaton.states.values (): + for s in automaton.iter_states (): o.write (' %s\n' % s.name) - for tr in s.transitions.values (): - for br in tr.branches.values (): + for tr in s.iter_transitions (): + for br in tr.iter_branches (): o.write (' %(state)s -> %(to)s [ label = "%(event)s" ];\n' % dict ( state = s.name, diff --git a/tools/dfagen/dfagen/parser.g b/tools/dfagen/dfagen/parser.g index 62f9f4d1..e510ed22 100644 --- a/tools/dfagen/dfagen/parser.g +++ b/tools/dfagen/dfagen/parser.g @@ -10,6 +10,7 @@ parser AutomatonParser: token EVENT: "\w([\w ]*\w)?" token QUALIFIER: "\w([\w ]*\w)?" token ATITLE: ".*?\n" + token ATTR: "\w([\w =]*\w)?" rule automaton: ATITLE {{ a = Automaton (ATITLE.strip ()) }} ( comments {{ a.comments = comments }} @@ -23,13 +24,19 @@ parser AutomatonParser: ( transdef<> ) * EOF {{ return a }} - - rule statedef: " " STATE {{ s = State (STATE) }} + + rule statedef: {{ initial = False }} + " " ( "\*" {{ initial = True }} + ) ? + STATE {{ s = State (STATE, initial = initial) }} + ( "\s*\[\s*" + ATTR {{ s.attributes = ATTR }} + "\s*\]" ) ? "\n" ( comments {{ s.comments = comments }} ) ? {{ return s }} - + rule eventdef: " " EVENT {{ e = Event (EVENT) }} "\n" ( comments {{ e.comments = comments }} @@ -52,6 +59,9 @@ parser AutomatonParser: "\s*->\s*" ( STATE {{ t.to = a.states[STATE] }} | "\\." ) + ( "\s*\[\s*" + ATTR {{ t.attributes = ATTR }} + "\s*\]" ) ? ( comments {{ t.comments = comments }} ) ? {{ return t }} diff --git a/tools/dfagen/examples/ex1_cb.c.patch b/tools/dfagen/examples/ex1_cb.c.patch index bc4cc924..48941dc5 100644 --- a/tools/dfagen/examples/ex1_cb.c.patch +++ b/tools/dfagen/examples/ex1_cb.c.patch @@ -7,7 +7,7 @@ +#include + +struct door_t { ex1_state_t fsm; }; -+ ++ /* * OPEN =close=> * => CLOSED diff --git a/tools/dfagen/examples/ex2.fsm b/tools/dfagen/examples/ex2.fsm index e290c05c..8142ad88 100644 --- a/tools/dfagen/examples/ex2.fsm +++ b/tools/dfagen/examples/ex2.fsm @@ -3,9 +3,9 @@ Example 2 A barman robot. States: - IDLE + IDLE [in=hello out=goodbye] waiting for a command - DROPPING_ICE + DROPPING_ICE [in=hi] FILLING_GLASS Events: @@ -15,13 +15,13 @@ Events: replace bottle IDLE: - command: with ice -> DROPPING_ICE + command: with ice -> DROPPING_ICE [ex2_idle_command] open the ice door command: without ice -> FILLING_GLASS start filling command: empty bottle -> . display "empty bottle, please replace it" - replace bottle -> . + replace bottle -> . [ex2_idle_replace] reset glass counter DROPPING_ICE: diff --git a/tools/dfagen/examples/ex2_cb.c.patch b/tools/dfagen/examples/ex2_cb.c.patch index f1402431..09f03791 100644 --- a/tools/dfagen/examples/ex2_cb.c.patch +++ b/tools/dfagen/examples/ex2_cb.c.patch @@ -1,5 +1,5 @@ ---- ex2_cb_skel.c 2008-01-06 18:02:50.000000000 +0100 -+++ ex2_cb.c 2008-01-06 18:02:50.000000000 +0100 +--- ex2_cb_skel.c 2008-06-10 17:47:09.000000000 +0200 ++++ ex2_cb.c 2008-06-10 17:47:09.000000000 +0200 @@ -6,6 +6,9 @@ * A barman robot. */ @@ -9,31 +9,14 @@ +#include /* - * FILLING_GLASS =command=> -@@ -37,6 +40,7 @@ + * IDLE =command=> +@@ -19,9 +22,25 @@ ex2_branch_t - ex2__FILLING_GLASS__glass_filled (robot_t *user) - { -+ puts ("stop filling"); - return ex2_next (FILLING_GLASS, glass_filled); - } - -@@ -48,6 +52,8 @@ - ex2_branch_t - ex2__IDLE__replace_bottle (robot_t *user) - { -+ puts ("reset glass counter"); -+ user->bottle = 3; - return ex2_next (IDLE, replace_bottle); - } - -@@ -63,9 +69,25 @@ - ex2_branch_t - ex2__IDLE__command (robot_t *user) + ex2_idle_command (robot_t *user) { -- return ex2_next_branch (IDLE, command, empty_bottle); -- return ex2_next_branch (IDLE, command, without_ice); - return ex2_next_branch (IDLE, command, with_ice); +- return ex2_next_branch (IDLE, command, without_ice); +- return ex2_next_branch (IDLE, command, empty_bottle); + if (user->bottle) + { + user->bottle--; @@ -56,7 +39,16 @@ } /* -@@ -99,6 +121,8 @@ +@@ -32,6 +51,8 @@ + ex2_branch_t + ex2_idle_replace (robot_t *user) + { ++ puts ("reset glass counter"); ++ user->bottle = 3; + return ex2_next (IDLE, replace_bottle); + } + +@@ -44,6 +65,8 @@ ex2_branch_t ex2__DROPPING_ICE__ice_dropped (robot_t *user) { @@ -65,3 +57,11 @@ return ex2_next (DROPPING_ICE, ice_dropped); } +@@ -77,6 +100,7 @@ + ex2_branch_t + ex2__FILLING_GLASS__glass_filled (robot_t *user) + { ++ puts ("stop filling"); + return ex2_next (FILLING_GLASS, glass_filled); + } + diff --git a/tools/dfagen/examples/tpl/template_defs.h b/tools/dfagen/examples/tpl/template_defs.h index 54fabba4..e4e29fd3 100644 --- a/tools/dfagen/examples/tpl/template_defs.h +++ b/tools/dfagen/examples/tpl/template_defs.h @@ -15,11 +15,11 @@ enum %(prefix)s_state_t typedef enum %(prefix)s_state_t %(prefix)s_state_t; /* %(name)s events. */ -enum %(prefix)s_event_t +enum %(prefix)s_event_type_t { -%(events)s %(PREFIX)s_EVENT_NB +%(events,%(PREFIX)s_EVENT_TYPE_%(event)s)s %(PREFIX)s_EVENT_TYPE_NB }; -typedef enum %(prefix)s_event_t %(prefix)s_event_t; +typedef enum %(prefix)s_event_type_t %(prefix)s_event_type_t; /* Only care about next state. */ #define _BRANCH(state, event, to) (%(PREFIX)s_STATE_ ## to) @@ -43,4 +43,7 @@ typedef %(prefix)s_branch_t (*%(prefix)s_transition_t) (void); #define %(prefix)s_next_branch(state, event, branch) \ %(PREFIX)s_BRANCH__ ## state ## __ ## event ## __ ## branch +/* Number of initial events. */ +#define %(PREFIX)s_INITIALS_NB %(initials_nb)s + #endif /* %(prefix)s_defs_h */ diff --git a/tools/dfagen/examples/tpl/template_table.h b/tools/dfagen/examples/tpl/template_table.h index 817197fa..39f07bdb 100644 --- a/tools/dfagen/examples/tpl/template_table.h +++ b/tools/dfagen/examples/tpl/template_table.h @@ -7,6 +7,20 @@ /* %(name)s transition table. */ static const %(prefix)s_transition_t -%(prefix)s_transition_table[%(PREFIX)s_STATE_NB][%(PREFIX)s_EVENT_NB] = { +%(prefix)s_transition_table[%(PREFIX)s_STATE_NB][%(PREFIX)s_EVENT_TYPE_NB] = { %(transition_table)s}; +/* %(name)s only branch table. */ +static const %(prefix)s_state_t +%(prefix)s_only_table[%(PREFIX)s_STATE_NB][%(PREFIX)s_EVENT_TYPE_NB] = { +%(only_branch_table)s}; + +/* %(name)s initial states. */ +static const %(prefix)s_state_t +%(prefix)s_initials_table[] = { +%(initials)s}; + +/* %(name)s state attributes. */ +static const %(prefix)s_state_t +%(prefix)s_attr_table[] = { +%(states,"%(state)s (%(@)s) (%(@in)s) (%(@out|no_out)s)")s}; -- cgit v1.2.3