Skip to content

Commit 0cf4dda

Browse files
authored
Cleanup (#95)
2 parents ae663af + 7b45c8d commit 0cf4dda

File tree

9 files changed

+146
-160
lines changed

9 files changed

+146
-160
lines changed

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,9 @@ More information can be added to the right side by calling `self.publish_debug_d
170170
## DSD Development GUI
171171
If you want to use a graphical user interface to define your DSD, we recommend [this repository](https://github.com/bit-bots/dsd-gui)
172172

173+
## VSCode Extension
174+
If you use VSCode, you can use the [DSD extension](https://marketplace.visualstudio.com/items?itemName=Mastermori.dsd) which provides syntax highlighting.
175+
173176
## Examples
174177

175178
Here are a few projects that use the DSD and can be used for reference:

dynamic_stack_decider/dynamic_stack_decider/dsd.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -153,16 +153,18 @@ def _bind_modules(self, element):
153153
:param element: The starting element
154154
"""
155155
if isinstance(element, ActionTreeElement):
156+
assert element.name in self.actions, f'Provided element "{element.name}" was not found in registered actions!'
156157
element.module = self.actions[element.name]
157158
elif isinstance(element, DecisionTreeElement):
159+
assert element.name in self.decisions, f'Provided element "{element.name}" was not found in registered decisions!'
158160
element.module = self.decisions[element.name]
159161
for child in element.children.values():
160162
self._bind_modules(child)
161163
elif isinstance(element, SequenceTreeElement):
162164
for action in element.action_elements:
163165
self._bind_modules(action)
164166
else:
165-
raise KeyError('Provided element ' + str(element) + 'was not found in registered actions or decisions')
167+
raise ValueError(f'Unknown parser tree element type "{type(element)}" for element "{element}"!')
166168

167169
def _init_element(self, element):
168170
""" Initialises the module belonging to the given element. """

dynamic_stack_decider/dynamic_stack_decider/parser.py

+5-7
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
11
import copy
22
import re
3-
from dynamic_stack_decider.tree import Tree, AbstractTreeElement, DecisionTreeElement, ActionTreeElement, \
4-
SequenceTreeElement
3+
from dynamic_stack_decider.tree import Tree, DecisionTreeElement, ActionTreeElement, SequenceTreeElement
54
import yaml
6-
import rclpy
75
from rclpy.node import Node
8-
from typing import List, Union
6+
from typing import Optional
97

108

119
class DsdParser:
12-
def __init__(self, node):
10+
def __init__(self, node: Optional[Node] = None):
1311
self.node = node
1412

15-
def parse(self, file_path):
13+
def parse(self, file_path: str) -> Tree:
1614
"""
1715
Parse a .dsd file to a Tree
1816
@@ -202,7 +200,7 @@ def _extract_parameters(self, token, lnr):
202200
'Did you forget a comma?'.format(lnr))
203201

204202
if parameter_value.startswith('%'):
205-
parameter_value = self.node.get_parameter(parameter_value[1:]).value
203+
parameter_value = self.node.get_parameter(parameter_value[1:]).value if self.node is not None else None
206204
parameter_dict[parameter_key] = parameter_value
207205
elif parameter_value.startswith('*'):
208206
# This is a reference to the value specified in the subtree

dynamic_stack_decider/dynamic_stack_decider/tree.py

+7-9
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,14 @@ class Tree:
66

77
def __init__(self):
88
# The root element of the tree
9-
self.root_element = None
9+
self.root_element: Optional[AbstractTreeElement] = None
1010
self.parameter_list = []
1111

12-
def set_root_element(self, element):
12+
def set_root_element(self, element: "AbstractTreeElement"):
1313
"""
1414
Set the root element of the tree
1515
1616
:param element: The root element
17-
:type element: AbstractTreeElement
1817
"""
1918
self.root_element = element
2019

@@ -28,15 +27,14 @@ class AbstractTreeElement:
2827
use one of DecisionTreeElement and ActionTreeElement instead
2928
"""
3029

31-
def __init__(self, name, parent):
30+
def __init__(self, name: str, parent: Optional["AbstractTreeElement"]):
3231
self.name = name
3332
self.parent = parent
3433
self.module = None
3534
self.parameters = None
3635
self.activation_reason = None
3736

38-
def get_child(self, activating_result):
39-
# type: (str) -> Optional[AbstractTreeElement]
37+
def get_child(self, activating_result: str) -> Optional["AbstractTreeElement"]:
4038
"""
4139
Get the child that should be activated for the given result.
4240
This makes only sense for DecisionTreeElements; others return None
@@ -70,14 +68,14 @@ def __init__(self, name, parent, parameters=None, unset_parameters=None):
7068
self.unset_parameters = unset_parameters or dict()
7169

7270
# Dictionary that maps results of the decision to the corresponding child
73-
self.children = dict()
71+
self.children: dict[str, AbstractTreeElement] = dict()
7472

75-
def add_child_element(self, element, activating_result):
73+
def add_child_element(self, element: AbstractTreeElement, activating_result: str):
7674
"""Add a child that will be executed when activating_result is returned"""
7775
self.children[activating_result] = element
7876
element.set_activation_reason(activating_result)
7977

80-
def get_child(self, activating_result):
78+
def get_child(self, activating_result: str) -> Optional[AbstractTreeElement]:
8179
"""Get the child for a given result"""
8280
if not activating_result:
8381
# give advice about error

dynamic_stack_decider/setup.py

+2-6
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,9 @@
1414
install_requires=[
1515
'setuptools',
1616
],
17-
test_suite='tests',
17+
tests_require=['pytest'],
1818
zip_safe=True,
1919
keywords=['ROS'],
2020
license='MIT',
21-
entry_points={
22-
# 'console_scripts': [
23-
# 'dynamic_stack_decider = dynamic_stack_decider.parser:parse',
24-
# ],
25-
}
21+
entry_points={}
2622
)
File renamed without changes.
File renamed without changes.
+126
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import os
2+
import pytest
3+
4+
from dynamic_stack_decider.parser import DsdParser, Tree
5+
from dynamic_stack_decider.tree import DecisionTreeElement, ActionTreeElement, SequenceTreeElement
6+
7+
8+
@pytest.fixture()
9+
def dsd_tree():
10+
parser = DsdParser()
11+
yield parser.parse(os.path.join(os.path.dirname(__file__), 'test.dsd'))
12+
13+
14+
def test_root_element(dsd_tree: Tree):
15+
root_element = dsd_tree.root_element
16+
assert isinstance(root_element, DecisionTreeElement)
17+
assert root_element.name == 'FirstDecision'
18+
19+
def test_possible_results(dsd_tree: Tree):
20+
assert set(dsd_tree.root_element.children.keys()) == \
21+
{'ACTION', 'DECISION', 'SUBBEHAVIOR', 'SEQUENCE', 'PARAMETERS',
22+
'LINE_COMMENT', 'BLOCK_COMMENT', 'COMPLICATED_COMMENT',
23+
'MULTIPLE_PARAMETERS', 'SECOND_SUBBEHAVIOR_1', 'SECOND_SUBBEHAVIOR_2',
24+
'PARAMETER_DECISION', 'PARAMETER_SUBBEHAVIOR', 'NESTED_PARAMETER_SUBBEHAVIOR',
25+
'SEQUENCE_TREE'}
26+
27+
def test_following_elements(dsd_tree: Tree):
28+
first_child = dsd_tree.root_element.get_child('ACTION')
29+
assert first_child.name == 'FirstAction'
30+
assert isinstance(first_child, ActionTreeElement)
31+
32+
second_child = dsd_tree.root_element.get_child('DECISION')
33+
assert second_child.name == 'SecondDecision'
34+
assert isinstance(second_child, DecisionTreeElement)
35+
36+
def test_nested_decision(dsd_tree: Tree):
37+
decision_child = dsd_tree.root_element.get_child('DECISION')
38+
assert set(decision_child.children.keys()) == {'FIRST', 'SECOND'}
39+
assert decision_child.get_child('FIRST').name == 'FirstAction'
40+
assert isinstance(decision_child.get_child('FIRST'), ActionTreeElement)
41+
assert decision_child.get_child('SECOND').name == 'SecondAction'
42+
assert isinstance(decision_child.get_child('SECOND'), ActionTreeElement)
43+
44+
def test_sub_behavior(dsd_tree: Tree):
45+
sub_behavior_root_decision = dsd_tree.root_element.get_child('SUBBEHAVIOR')
46+
assert sub_behavior_root_decision.name == 'ThirdDecision'
47+
assert set(sub_behavior_root_decision.children.keys()) == {'FIRST', 'SECOND'}
48+
assert sub_behavior_root_decision.get_child('FIRST').name == 'FirstAction'
49+
assert isinstance(sub_behavior_root_decision.get_child('FIRST'), ActionTreeElement)
50+
assert sub_behavior_root_decision.get_child('SECOND').name == 'SecondAction'
51+
assert isinstance(sub_behavior_root_decision.get_child('SECOND'), ActionTreeElement)
52+
53+
def test_sequence_element(dsd_tree: Tree):
54+
sequence_element = dsd_tree.root_element.get_child('SEQUENCE')
55+
assert isinstance(sequence_element, SequenceTreeElement)
56+
assert len(sequence_element.action_elements) == 2
57+
first_action = sequence_element.action_elements[0]
58+
assert first_action.name == 'FirstAction'
59+
assert isinstance(first_action, ActionTreeElement)
60+
second_action = sequence_element.action_elements[1]
61+
assert second_action.name == 'SecondAction'
62+
assert isinstance(second_action, ActionTreeElement)
63+
64+
def test_action_parameters(dsd_tree: Tree):
65+
parameter_element = dsd_tree.root_element.get_child('PARAMETERS')
66+
assert parameter_element.name == 'FirstAction'
67+
assert isinstance(parameter_element, ActionTreeElement)
68+
assert parameter_element.parameters == {'key': 'value'}
69+
70+
def test_decision_parameters(dsd_tree: Tree):
71+
parameter_element = dsd_tree.root_element.get_child('PARAMETER_DECISION')
72+
assert parameter_element.name == 'FirstDecision'
73+
assert isinstance(parameter_element, DecisionTreeElement)
74+
assert parameter_element.parameters == {'key': 'value'}
75+
76+
def test_line_comment(dsd_tree: Tree):
77+
comment_element = dsd_tree.root_element.get_child('LINE_COMMENT')
78+
assert comment_element.name == 'FirstAction'
79+
assert isinstance(comment_element, ActionTreeElement)
80+
81+
def test_block_comment(dsd_tree: Tree):
82+
comment_element = dsd_tree.root_element.get_child('BLOCK_COMMENT')
83+
assert comment_element.name == 'FirstAction'
84+
assert isinstance(comment_element, ActionTreeElement)
85+
assert comment_element.parameters == {'key': 'value'}
86+
87+
def test_complicated_comment(dsd_tree: Tree):
88+
comment_element = dsd_tree.root_element.get_child('COMPLICATED_COMMENT')
89+
assert comment_element.name == 'FirstAction'
90+
assert isinstance(comment_element, ActionTreeElement)
91+
92+
def test_multiple_parameters(dsd_tree: Tree):
93+
parameter_element = dsd_tree.root_element.get_child('MULTIPLE_PARAMETERS')
94+
assert parameter_element.name == 'FirstAction'
95+
assert isinstance(parameter_element, ActionTreeElement)
96+
assert parameter_element.parameters == {'key1': 'value1', 'key2': 'value2'}
97+
98+
def test_multiple_subbehavior_references(dsd_tree: Tree):
99+
sub_behavior_1_root_decision = dsd_tree.root_element.get_child('SECOND_SUBBEHAVIOR_1')
100+
sub_behavior_2_root_decision = dsd_tree.root_element.get_child('SECOND_SUBBEHAVIOR_2')
101+
assert sub_behavior_1_root_decision.name == sub_behavior_2_root_decision.name
102+
assert sub_behavior_1_root_decision.activation_reason == 'SECOND_SUBBEHAVIOR_1'
103+
assert sub_behavior_2_root_decision.activation_reason == 'SECOND_SUBBEHAVIOR_2'
104+
105+
def test_parameter_subbehavior(dsd_tree: Tree):
106+
parameter_subbehavior_decision = dsd_tree.root_element.get_child('PARAMETER_SUBBEHAVIOR')
107+
first_action = parameter_subbehavior_decision.get_child('FIRST')
108+
assert first_action.parameters == {'key': 'value1'}
109+
action_sequence = parameter_subbehavior_decision.get_child('SECOND')
110+
assert action_sequence.action_elements[0].parameters == {'key': 'value2'}
111+
assert action_sequence.action_elements[1].parameters == {'key': 'value2'}
112+
113+
def test_nested_parameter_subbehavior(dsd_tree: Tree):
114+
subbehavior_decision = dsd_tree.root_element.get_child('NESTED_PARAMETER_SUBBEHAVIOR')
115+
inner_subbehavior_decision = subbehavior_decision.get_child('FIRST')
116+
first_action = inner_subbehavior_decision.get_child('FIRST')
117+
assert first_action.parameters == {'key': 'nested1'}
118+
action_sequence = inner_subbehavior_decision.get_child('SECOND')
119+
assert action_sequence.action_elements[0].parameters == {'key': 'nested2'}
120+
assert action_sequence.action_elements[1].parameters == {'key': 'nested2'}
121+
122+
def test_sequence_tree(dsd_tree: Tree):
123+
sequence_tree = dsd_tree.root_element.get_child('SEQUENCE_TREE')
124+
assert isinstance(sequence_tree, SequenceTreeElement)
125+
assert sequence_tree.action_elements[0].name == 'FirstAction'
126+
assert sequence_tree.action_elements[1].name == 'SecondAction'

dynamic_stack_decider/tests/test_parser.py

-137
This file was deleted.

0 commit comments

Comments
 (0)