aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNathanael Sensfelder <SpamShield0@MultiAgentSystems.org>2016-10-05 15:11:54 +0200
committerNathanael Sensfelder <SpamShield0@MultiAgentSystems.org>2016-10-05 15:11:54 +0200
commitb1d478ec9adef50a4ce0891fd18b81b937ab41cd (patch)
tree175c6b746dd85ec057f23bb96b8c4af946a45320
parent163f7bd1f595afaa2f445bafb95d15304396d9f4 (diff)
downloadnet2rdp-b1d478ec9adef50a4ce0891fd18b81b937ab41cd.zip
net2rdp-b1d478ec9adef50a4ce0891fd18b81b937ab41cd.tar.bz2
Adds support for ACTION and CONDITION in PN?
-rw-r--r--CHANGELOG4
-rwxr-xr-xtina_converter.py250
2 files changed, 231 insertions, 23 deletions
diff --git a/CHANGELOG b/CHANGELOG
index ad3b735..6a6b7e2 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -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)