at Fri, 27 Mar 2009 12:01:21 +0100, Hans Breuer wrote:

> > Hi,
> >is there some way to store information in the dia objects during export
> >with a python plugin?
> Depends on what version of Dia you are using. With 0.97-pre there is
> generic object metainfo. Just use:
>       obj.properties["meta"] = {'mykey' : 'myvalue' }
> which then gets stored and loaded with Dia's standard load/save.
Thanks. I'll try that. I had 0.96.1. So today I fetched 0.97... from svn.
        
> Is this generate from PyDia? Maybe you can show some source?
> (Given the recent increase in PyDia questions I wonder why there are
> no user provide PyDia plug-ins. Are you all just developing
> proprietary stuff?)
No, it's not proprietary. And I plan to submit a patch quite soon. The current 
(alpha!!!) version (not cleaned up, still using a dictionary instead of 
metainfo) is appended.

Sebastian

# State-Chart Code Generation from UML Diagram
# Copyright (c) 2009  Sebastian Setzer

# Use at your own risk. There's no support, no warranty, nothing at all for this prototype version.

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# templates

# header
t_hdr = '''
---                                           --- start
#ifndef $include_guard
#define $include_guard

class $classname
{
	enum State
	{
---                                           --- enum_value
		$name = $id,
---                                           --- between_enum_and_triggers
		STATE_COUNT = $state_count
	} m_states;

	void setState(State state);
	void do();

public:
	$classname();
---                                           --- triggerdecl
	void $trigger();
---                                           --- end
}

#endif /* $include_guard */
'''

# compilation unit
t_cpp = '''
---                                           --- start
#include "$filename.h"

$classname::$classname()
: m_states(START)
{}

void $classname::setState(State state)
{
	// exit old state
	switch(m_state)
	{
---                                           --- case
	case $label:
---                                           --- action
		$action;
---                                           --- guarded_action
		if($guard)
			$action;
---                                           --- case_break
		break;
---                                           --- between_exit_and_enter
	}
	// enter new state
	m_state = state;
	switch(m_state)
	{
---                                           --- setstate_end
	}
}
---                                           --- do_start

void $classname::do()
{
	switch(m_state)
	{
---                                           --- do_end
	}
}
---                                           --- triggerdef_start

void $classname::$trigger()
{
	switch(m_state)
	{
---                                           --- triggerdef_guard_start
		if($guard)
		{
---                                           --- triggerdef_guarded_action
			$action;
---                                           --- triggerdef_guard_end
		}
---                                           --- error_multiple_unguarded_transitions
	Error: multiple unguarded transitions.
---                                           --- triggerdef_action
		$action;
---                                           --- triggerdef_end
	}
}
---                                           --- end

// end of $classname
'''

# substitute into a string, not into a file:
t_sub = '''
---                                           --- triggerdef_setstate -n
setState($state)
'''

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

import sys, dia, re, os.path
from string import Template
import re
sep_re = re.compile('---(.*)\n', re.M)

class Templates:
	def __init__(self, template_string):
		parts = sep_re.split(template_string)[1:]
		self.templates = {}
		while parts:
			(sep_line, template), parts = parts[:2], parts[2:]
			argv=sep_line.strip('- ').split()
			key=argv[0]
			for arg in argv[1:]:
				# no last newline, as in echo -n
				if arg=='-n' and template.endswith('\n'): template=template[:-1]
				else: raise '%s: ununsed argument "%s"' % (key, arg)
			self.templates[key] = Template(template)
		
	def __getattr__(self, name):
		return self.templates[name].substitute

class FileTemplates(Templates):
	def __init__(self, file, template_string):
		Templates.__init__(self, template_string)
		self.file=file
	def __getattr__(self, name):
		return lambda **kws: self.file.write(self.templates[name].substitute(kws))

def transition_label(o):
	l = o.properties["trigger"].value
	if o.properties["guard"].value:
		l += " [%s]" % o.properties["guard"].value
	if o.properties["action"].value:
		l += " / %s" % o.properties["action"].value
	return l

def char2cpp(c):
	if not c.isalnum(): return '_'
	return c
def make_t_cpp_id(s):
	s = ''.join(map(char2cpp, s))
	if not s: s = '_'
	if s[0].isdigit(): s = '_' + s
	return s

# For every edge there is a trigger, an guard and an action attribute.
# For states, there are 3 predefined triggers entry, do, exit.
# Unfortunately, these have only actions, no guards.
# This function checks if the action matches a "[guard] action" - pattern.
# If it does, it extracts the guard.
def extract_guard(action):
	action = action.strip()
	if action.startswith('['):
		level = 0
		for i,c in enumerate(action):
			if c=='[':
				level += 1
			elif c==']':
				level -= 1
				if level == 0: # found matching ']'
					return action[1:i].strip(), action[i+1:].strip()
	return None, action

class Rectangle:
	def __init__(self, o):
		self.x1, self.y1, self.x2, self.y2 = tuple(o.properties["obj_bb"].value)
def inside(state,cluster):
	s = Rectangle(state)
	c = Rectangle(cluster)
	return (s.x1 >= c.x1) and (s.x2 <= c.x2) and (s.y1 >= c.y1) and (s.y2 <= c.y2)

# class for dia_dict of dia state objects
def State_reset():
	State.dia_dict={}
	State.start_ids = 0
	State.final_ids = 0
class State:
	dia_dict={}
	start_ids = 0
	final_ids = 0
	def __init__(self, o):
		self.entry_guard, self.entry_action, self.do_guard, self.do_action, self.exit_guard, self.exit_action = 6*(None,)
		
		if o.type.name == "UML - State":
			self.name = o.properties["text"].value.text.strip()			
			entry, do, exit = o.properties["entry_action"].value, o.properties["do_action"].value, o.properties["exit_action"].value
			if entry: self.entry_guard, self.entry_action = extract_guard(entry)
			if do: self.do_guard, self.do_action = extract_guard(do)
			if exit: self.exit_guard, self.exit_action = extract_guard(exit)			
		elif o.type.name == "UML - State Term":
			if o.properties["is_final"].value:
				# final state
				self.name = "FINAL_%d" % State.final_ids
				State.final_ids += 1
			else:
				# start state
				self.name = "START"
				if State.start_ids: name = "START_%d" % State.start_ids
				State.start_ids += 1

def dia_dict(o):
	return State.dia_dict[o]

def output(filename, start_states, final_states, normal_states, clusters, edges, errors):
	filename, ext = os.path.splitext(filename)
	path, filename = os.path.split(filename)
	classname = filename
	filename = filename.lower()
	include_guard = '%s_H' % filename.upper()
	path =  os.path.join(path, filename)
	f_hdr = open(path + ".h", "w")
	f_cpp = open(path + ".cpp", "w")
	hdr = FileTemplates(f_hdr, t_hdr)
	cpp = FileTemplates(f_cpp, t_cpp)
	sub = Templates(t_sub)

	triggers = set()
	transition_table = {}
	cluster_states = {}
	
	# Output is done in this order, which is probably creation order in dia for the 3 groups of states.
	# I think that's quite good for version control of the generated files: states won't get reordered when renamed.
	all_states = start_states + normal_states + final_states
	
	# create State objects
	for o in all_states:
		State.dia_dict[o] = State(o)
	
	# use order of all_states instead of unsorted State.dia_dict.values()
	all_gen_info = map(lambda o: dia_dict(o), all_states)
	
	# find states which are geometrically inside clusters
	for c in clusters:
		cluster_states[c] = filter(lambda s: inside(s,c), all_states)
	# TODO:
	#
	# Edges
	# ----------
	# An edge with cluser-tgt is equivalent to an edge to the (unique) start state of the cluster
	# An edge with cluser-src is equivalent to lots of edges with state-src.
	# In Triggers of cluster-edges, "case" could be combined, so the body doesn't need to be duplicated.
	# But it would be even more general if we had an "equal body recognition" for switch-es,
	# So that could compress non-cluster-switches, too.
	# Like this: for each case: d[body].append(case.label) ...
	# For this, abstract the different switch-outputs below to a switch-output-function.
	# 
	# Actions
	# ------------
	# do-actions sind einfach: Hinzufuegen zu den do-actions der enthaltenen states.
	# entry- und exit actions: jeder state gehoert zu einer Menge von cluster_states.
	# Bei Uebergang wird entry fuer alle cluster_states aufgerufen, die in der Menge
	# der cluster_states des alten states nicht drin waren, aber in der des neuen
	# states drin sind. exit-action analog.
	# Problem: Ein switch in setState ueber alten bzw. neuen state reicht nicht mehr,
	# es muss ein switch uexxdiff . ueber die Kombi aus altem+neuem state sein (z.B. verschachtelter switch).
	# "wenn neuer state im cluster (erster switch) und alter state nicht im cluster (zweiter switch)
	# dann entry action des clusters" ist auch nichts anderes als verschachtelter switch,
	# mit dem Nachteil dass mans fuer jeden cluster extra machen muss.
	# --> Es darf nicht in setState sondern muss in die Ereignis-Funktionen (die entsprechen den edges)?
	# Am besten nicht die actions direkt eingefuegt sondern:
	# 	leaveCluster(C_old1)
	# 	leaveCluster(C_old2)
	# 	setState(s_new)
	# 	enterCluster(C_new1)
	# Trotzdem wie oben beschrieben: Cluster-edges vorher in (mehrere) state-edges
	# umwandeln, erst dann Menge von leave/enter-clusters bestimmen.
	# leave/enter soll naemlich auch f�r edges aufgerufen werden, die an normalen
	# states haengen aber die "Grenzen" eines clusters ueberschreiten.
	#~ for c in clusters:
		#~ print "Cluster", c.properties["text"].value.text
		#~ for s in cluster_states[c]:
			#~ print "    ", dia_dict(s).name

	# fill transition_table: dictionary (trigger, src) --> list of (edge-object, src, tgt)
	for (e, src, tgt) in edges:
		trigger = make_t_cpp_id(e.properties["trigger"].value.strip())
		if trigger != "otherwise":
			triggers.add(trigger)
		
		# Handle clusters
		if src in cluster_states:
			src_states = cluster_states[src]
		else:
			src_states = [src]
		if tgt in cluster_states:
			# TODO:
			# cluster_start = intersection(cluster_states[tgt], start_states)
			# assert len(cluster_start) == 1
			# tgt = cluster_start[0]
			pass
		
		for src_state in src_states:
			# append (e, src_state, tgt) to transition_table[(trigger, src_state)]
			trans = transition_table.setdefault((trigger, src_state), [])
			trans.append((e, src_state, tgt))
			transition_table[(trigger, src_state)] = trans

	# output hdr
	hdr.start(include_guard=include_guard, classname=classname)

	for i, s in enumerate(all_gen_info):
		hdr.enum_value(name=s.name, id=i)
		
	hdr.between_enum_and_triggers(state_count=len(all_states), classname=classname)

	for t in sorted(triggers):
		hdr.triggerdecl(trigger=t)

	hdr.end(include_guard=include_guard)

	# output cpp
	cpp.start(filename=filename, classname=classname)

	for s in all_gen_info:
		cpp.case(label=s.name)
		if s.exit_action:
			if s.exit_guard:
				cpp.guarded_action(guard=s.exit_guard, action=s.exit_action)
			else:
				cpp.action(action=s.exit_action)
		cpp.case_break()
	cpp.between_exit_and_enter()
	for s in all_gen_info:
		cpp.case(label=s.name)
		if s.entry_action:
			if s.entry_guard:
				cpp.guarded_action(guard=s.entry_guard, action=s.entry_action)
			else:
				cpp.action(action=s.entry_action)
		cpp.case_break()
	cpp.setstate_end()
	
	cpp.do_start(classname=classname)
	for s in all_gen_info:
		cpp.case(label=s.name)
		if s.do_action:
			if s.do_guard:
				cpp.guarded_action(guard=s.do_guard, action=s.do_action)
			else:
				cpp.action(action=s.do_action)
		cpp.case_break()
	cpp.do_end()

	for t in sorted(triggers):
		cpp.triggerdef_start(classname=classname, trigger=t)

		for o in all_states:
			translist = transition_table.setdefault((t, o), [])
			if not translist:
				translist = transition_table.setdefault(("otherwise", o), [])
			if translist:
				cpp.case(label=dia_dict(o).name)
				unguarded = None
				for (edge, src, tgt) in sorted(translist, key=lambda (edge, src, tgt): edge.properties["guard"].value):
					# Multiple edges with the same trigger (but different guards)
					# must go into the same "case".
					if edge.properties["guard"].value:
						cpp.triggerdef_guard_start(guard=edge.properties["guard"].value)
						if edge.properties["action"].value:
							cpp.triggerdef_guarded_action(action=edge.properties["action"].value)
						if src != tgt:
							cpp.triggerdef_guarded_action(
								action=sub.triggerdef_setstate(state=dia_dict(tgt).name))
						cpp.triggerdef_guard_end()
					elif unguarded:
						cpp.error_multiple_unguarded_transitions()
					else:
						unguarded = (edge, src, tgt)
				if unguarded:
					(edge, src, tgt) = unguarded
					if edge.properties["action"].value:
						cpp.triggerdef_action(action=edge.properties["action"].value)
					if src != tgt:
						cpp.triggerdef_action(
							action=sub.triggerdef_setstate(state=dia_dict(tgt).name))
							
				cpp.case_break()

		cpp.triggerdef_end()


	cpp.end(classname=classname)
	f_cpp.close()
	f_hdr.close()

def is_uml_state(type):
	return (type == "UML - State") or (type == "UML - State Term") or (type == "UML - State Cluster")
	
class StateRenderer :
	"Transforms a diagram to a C++ State-Chart"
	def __init__(self) :
		pass
	def begin_render (self, data, filename) :
		normal_states = []
		start_states = []
		final_states = []
		clusters = []
		edges = []
		errors = []
		for layer in data.layers :
			# for the moment ignore layer info. But we could use this to spread accross different files
			for o in layer.objects :
				if o.type.name == "UML - State":
					normal_states.append(o)
				elif o.type.name == "UML - State Term":
					if o.properties["is_final"].value:
						final_states.append(o)
					else:
						start_states.append(o)
				elif o.type.name == "UML - Transition" :
					t = "Transition %s " % transition_label(o)
					c_src = o.handles[0].connected_to
					c_tgt = o.handles[1].connected_to
					if not (c_src and c_tgt):
						errors.append("%s is not connected on both sides" % t)
					else:
						src = c_src.object
						tgt = c_tgt.object
						if not is_uml_state(src.type.name):
							errors.append("The source of %s is a %s" % (t, src.type.name))
						elif not is_uml_state(tgt.type.name):
							errors.append("The target of %s is a %s" % (t, tgt.type.name))
						else:
							edges.append((o, src, tgt))
				elif o.type.name == "UML - State Cluster":
					clusters.append(o)
		#print start_states[0].properties.keys()
		output(filename, start_states, final_states, normal_states, clusters, edges, errors)
	def end_render(self) :
		# do cleanup here if you have set class members
		State_reset()

# dia-python keeps a reference to the renderer class and uses it on demand
dia.register_export ("State-Chart Code Generation (C++)", "cpp", StateRenderer())
_______________________________________________
dia-list mailing list
[email protected]
http://mail.gnome.org/mailman/listinfo/dia-list
FAQ at http://live.gnome.org/Dia/Faq
Main page at http://live.gnome.org/Dia

Reply via email to