aboutsummaryrefslogtreecommitdiff |
diff options
author | Nathanael Sensfelder <SpamShield0@MultiAgentSystems.org> | 2016-10-05 15:11:54 +0200 |
---|---|---|
committer | Nathanael Sensfelder <SpamShield0@MultiAgentSystems.org> | 2016-10-05 15:11:54 +0200 |
commit | b1d478ec9adef50a4ce0891fd18b81b937ab41cd (patch) | |
tree | 175c6b746dd85ec057f23bb96b8c4af946a45320 | |
parent | 163f7bd1f595afaa2f445bafb95d15304396d9f4 (diff) | |
download | net2rdp-b1d478ec9adef50a4ce0891fd18b81b937ab41cd.zip net2rdp-b1d478ec9adef50a4ce0891fd18b81b937ab41cd.tar.bz2 |
Adds support for ACTION and CONDITION in PN?
-rw-r--r-- | CHANGELOG | 4 | ||||
-rwxr-xr-x | tina_converter.py | 250 |
2 files changed, 231 insertions, 23 deletions
@@ -1,4 +1,4 @@ VERSION 2 (Oct. 4 2016): No longer require place names to follow one another (i.e. having only - places 0, 2, 99121) is now authorized. Before, if you had place 2, you - had to have both place 0 and 1. + places 0, 2, 99121 is now authorized). Before this version, if you had place + 2, you had to have both place 0 and 1. diff --git a/tina_converter.py b/tina_converter.py index f51d50e..8bdad33 100755 --- a/tina_converter.py +++ b/tina_converter.py @@ -4,6 +4,12 @@ import argparse import re +# Possible improvements +# - Remove the needless restriction on place names (pretty much anything should +# be fine, since we're renaming them anyway. +# - Allow for a '-direct' invocation parameter, letting the user directly type +# LeTos names and operators in the Petri net. + ## 'CONSTANTS' ################################################################ TINA_TRANSITION_REGEX = re.compile('tr t([0-9]+) : {(.*)} \[0,w\[ (.*) -> (.*)') TINA_PLACE_W_TOKEN_REGEX = re.compile('pl p([0-9]+) : .* \(([0-9]+)\)') @@ -16,6 +22,7 @@ CONDITION_OP['<='] = 2 CONDITION_OP['>='] = 3 CONDITION_OP['<'] = 4 CONDITION_OP['>'] = 5 +CONDITION_OPS = ['=', '<=', '>=', '<', '>'] CONDITION = dict() # (ID, MIN_VALUE, MAX_VALUE) @@ -49,14 +56,47 @@ ACTION['ENVOI_BT'] = (18, True) ## FUNCTIONS ################################################################## +#### TRANSLATION TABLE HANDLING ################################################ +# The translation table: +# Translate the name in Petri net to +# - ('action', LeJos_action_id) for actions without parameters. +# - ('action_w_param', LeJos_action_id, parameter_value) for actions with +# parameters. +# - ('condition', LeJos_condition_id, LeJos_operator_id, condition_value) for +# conditions. + def parse_translation_table (tt_file): tt = dict() + + if (tt_file is None): + return tt + for line in tt_file: data = line.replace('\n','').replace('\r', '').split("::") - if (len(data) == 2): # Action - if (data[0] in tt): - print('[W] Multiple definitions for symbol "' + data[0] + '"') + if ( + (data[0] in ACTION) + or (data[0] in CONDITION) + or (data[0] in CONDITION_OP) + ): + print( + '[E] Symbol "' + + data[0] + + '" in translation table would mask an existing LeJos symbol."' + ) + exit(-1) + + if (data[0] in tt): + print( + '[W] Multiple definitions for symbol "' + + data[0] + + '" in the translation table' + ) + + if (len(data) == 2): + # Only 'action without parameter' entries have two components: + # name_in_petri_net::LeJos_action_name + if (data[1] not in ACTION): print('[E] Unsupported action: "' + data[1] + '" in translation table.') @@ -66,7 +106,7 @@ def parse_translation_table (tt_file): if (act_param): print( - '[E] "' + '[E] Translation table entry "' + data[0] + '" uses "' + data[1] @@ -77,9 +117,8 @@ def parse_translation_table (tt_file): tt[data[0]] = ('action', act_id) elif (len(data) == 3): # Action with param - - if (data[0] in tt): - print('[W] Multiple definitions for symbol "' + data[0] + '"') + # Only 'action with parameter' entries have two components: + # name_in_petri_net::LeJos_action_name::parameter_value if (data[1] not in ACTION): print('[E] Unsupported action: "' + data[1] + '" in translation table.') @@ -94,8 +133,8 @@ def parse_translation_table (tt_file): tt[data[0]] = ('action_w_param', (act_id, data[2])) elif (len(data) == 4): # Condition - if (data[0] in tt): - print('[W] Multiple definitions for symbol "' + data[0] + '"') + # Only 'conditions' entries have two components: + # name_in_petri_net::LeJos_condition_name::operator::positive_integer if (data[1] not in CONDITION): print( @@ -109,7 +148,7 @@ def parse_translation_table (tt_file): if (not data[3].isdigit()): print( - '[E] "' + '[E] Translation table entry "' + data[0] + '" uses "' + data[3] @@ -135,7 +174,7 @@ def parse_translation_table (tt_file): if (data[2] not in CONDITION_OP): print( - '[E] "' + '[E] Translation table entry "' + data[0] + '" uses an unknown operator: "' + data[2] @@ -150,6 +189,11 @@ def parse_translation_table (tt_file): return tt +# Handles the mention of a place in a transition definition (with or +# without weight multiplier). +# 'pa' is the place_aliases dictionary, it removes the need to require places +# 1, 2, 3 just because we have the place 4. Indeed, the place numbers will be +# offset to ensure we have consecutive numbers. def handle_link (link, pa): if ('*' in link): data = link.split('*') @@ -164,7 +208,90 @@ def handle_link (link, pa): return ('X' + str(pa[link]) + '*1') +def attempt_direct_condition (cond): + operator = '' + + # Having multiple operators in the condition will trigger an error later: + # either the variable name will contain one of the operators, triggering + # an invalid variable error, or the number will contain it, triggering an + # invalid number error. + for op in CONDITION_OPS: + if (op in cond): + operator = op + break + + # No operator -> either an invalid action call, or an alias + if (operator == ''): + if (cond in ACTION): + print( + '[E] Action "' + + cond + + '" used as a condition directly in the Petri net.' + ) + exit(-1) + + return (False, None) + + # Otherwise, it's a condition written directly in the Petri net. + op_id = CONDITION_OP[operator] + + data = cond.split(operator) + + if ((data[0] == '') or (data[1] == '')): + print( + '[E] Invalid condition "' + + cond + + '" used in Petri net.' + ) + exit(-1) + + if (data[0] not in CONDITION): + print( + '[E] Variable "' + + data[0] + + '" is mentionned in the Petri as part of a condition ("' + + cond + + '"), but is not recognized as a condition variable.' + ) + exit(-1) + + if (not data[1].isdigit()): + print( + '[E] Condition "' + + cond + + '" used in Petri net does not compare a variable with an integer.' + ) + exit(-1) + + (cond_id, cond_min, cond_max) = CONDITION[data[0]] + + val = int(data[1]) + + if ((val < cond_min) or (val > cond_max)): + print( + '[E] Condition used in Petri net "' + + cond + + '" compares "' + + data[1] + + '" with a variable whose range is [' + + cond_min + + ', ' + + cond_max + + ']. Please stay in that range.' + ) + exit(-1) + + return (True, (str(cond_id) + ',' + str(op_id) + ',' + str(val))) + +# Handles a petri net name indicated as being a condition. def handle_condition (cond, tt): + cond = cond.replace(' ', '') + + (is_direct, result) = attempt_direct_condition(cond) + + if (is_direct): + return result + if (cond not in tt): print('[E] Token "' + cond + '" not defined in transition table.') exit(-1) @@ -172,14 +299,79 @@ def handle_condition (cond, tt): (d_type, data) = tt[cond] if (d_type != 'condition'): - print('[E] Token "' + cond + ' is incorrectly used as a condition.') + print('[E] Token "' + cond + '" is incorrectly used as a condition.') exit(-1) (c_id, c_op, c_val) = data return (str(c_id) + ',' + str(c_op) + ',' + str(c_val)) + +def attempt_direct_action (act): + # Is there an argument? + if (':' in act): + data = act.split(':') + + if ((data[0] == '') or (data[1] == '')): + print( + '[E] Invalid action "' + + act + + '" used in Petri net.' + ) + exit(-1) + + if (data[0] not in ACTION): + print( + '[E] In the Petri net, "' + + data[0] + + '" is used in an action with parameter ("' + + act + + '"), but is not recognized as a LeTos action.' + ) + exit(-1) + + (act_id, act_param) = ACTION[data[0]] + + if (not act_param): + print( + '[E] In the Petri net, "' + + data[0] + + '" is used in an action with parameter ("' + + act + + '"), but "' + + data[0] + + '" does not expect parameters.' + ) + exit(-1) + + return (True, (str(act_id) + ',' + data[1])) + else: + if (act not in ACTION): + return (False, None) + + (act_id, act_param) = ACTION[act] + + if (act_param): + print( + '[E] In the Petri net, "' + + act + + '" is used as an action without parameters, but "' + + data[0] + + '" expects parameters.' + ) + exit(-1) + + return (True, (str(act_id) + ',0')) + +# Handles a petri net name indicated as being an action. def handle_action (act, tt): + act = act.replace(' ', '') + + (is_direct, result) = attempt_direct_action(act) + + if (is_direct): + return result + if (act not in tt): print('[E] Token "' + act + '" not defined in transition table.') exit(-1) @@ -187,7 +379,7 @@ def handle_action (act, tt): (d_type, data) = tt[act] if (d_type != 'action' and d_type != 'action_w_param'): - print('[E] Token "' + cond + ' is incorrectly used as an action.') + print('[E] Token "' + cond + '" is incorrectly used as an action.') exit(-1) if (d_type == 'action'): @@ -205,12 +397,15 @@ def handle_transition (transition_id, label, inputs, outputs, tt, pa): conditions = label[0].split(',') actions = label[1].split(',') + # 'split' will return the '' element instead of an empty list. if ('' in conditions): conditions.remove('') if ('' in actions): actions.remove('') + # FIXME: Not having any conditions might not be allowed. + if (len(conditions) > 4): print('[E] Transition "' + transition_id + '" has too many conditions.') exit(-1) @@ -223,6 +418,7 @@ def handle_transition (transition_id, label, inputs, outputs, tt, pa): result += ','.join([handle_link(input_link, pa) for input_link in inputs]) if (len(conditions) == 0): + # This is what is returned by the original program, I won't question it. result += ';?' else: result += ';' + '/'.join([handle_condition(cond, tt) for cond in conditions]) @@ -230,6 +426,7 @@ def handle_transition (transition_id, label, inputs, outputs, tt, pa): result += ';' + ','.join([handle_link(output_link, pa) for output_link in outputs]) if (len(actions) == 0): + # This is what is returned by the original program, I won't question it. result += ';?' else: result += ';' + '/'.join([handle_action(act, tt) for act in actions]) @@ -242,11 +439,11 @@ def handle_transition (transition_id, label, inputs, outputs, tt, pa): def convert_tina_net (net_file, tt): first_line = "1,0" result = "" - tokens_at = dict() - places_aliases = dict() + tokens_at = dict() # in: place final number, out: initial number of tokens. + places_aliases = dict() # in: Petri net place name, out: place final number for line in net_file: - line = line.replace('\r','').replace('\n','') + line = line.replace('\r', '').replace('\n', '') if (line == ''): continue @@ -266,6 +463,8 @@ def convert_tina_net (net_file, tt): ) continue + # Make sure to test if the place indicates a token amount, because the + # next filter will overlook it (despite accepting the input). matched = TINA_PLACE_W_TOKEN_REGEX.search(line) if (matched): @@ -278,6 +477,8 @@ def convert_tina_net (net_file, tt): tokens_at[places_aliases['p' + matched.group(1)]] = '0' continue + # This is not actually used, but hey. Currently, the only point is to ensure + # that this line is considered as expected and will not trigger an error. matched = TINA_NET_REGEX.search(line) if (matched): @@ -287,14 +488,21 @@ def convert_tina_net (net_file, tt): print('[P] If this was unexpected, please inform the developer.') exit(-1) + # It appears we have to add an extra place for the output file to be accepted. first_line = ( str(len(tokens_at) + 1) + ',' - + ','.join([('0' if i not in tokens_at else tokens_at[i]) for i in range(0, len(tokens_at))]) + + ','.join( + [ + ('0' if i not in tokens_at else tokens_at[i]) + for i in range(0, len(tokens_at)) + ] + ) ) + ',0' return (first_line + result) +#### 'MAIN' FUNCTION ########################################################### parser = argparse.ArgumentParser( description='Converts a Tina NET file into a RDP one, using a translation table.' ) @@ -302,10 +510,12 @@ parser.add_argument( 'net_file', type=argparse.FileType(mode='r', encoding='UTF-8'), help='The Tinal NET file' -) # required to deal with N -> N transitions. +) parser.add_argument( - 'translation_table', + '-t', + '--translation-table', type=argparse.FileType(mode='r', encoding='UTF-8'), + required=False, help='The translation table file' ) parser.add_argument( @@ -319,5 +529,3 @@ translation_table = parse_translation_table(args.translation_table) converted_tn = convert_tina_net(args.net_file, translation_table) args.output_file.write(converted_tn) args.output_file.close() -#print("Result:") -#print(converted_tn) |