bryan-stearns commited on
Commit
786340e
1 Parent(s): c3d5c44

Initial app commit

Browse files
Files changed (5) hide show
  1. .gitignore +30 -0
  2. Inspect_Logic.py +101 -0
  3. README.md +1 -1
  4. smem_obj.py +276 -0
  5. smem_token_parser.py +140 -0
.gitignore ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Compiled class file
2
+ *.class
3
+
4
+ # Log file
5
+ *.log
6
+
7
+ # BlueJ files
8
+ *.ctxt
9
+
10
+ # Mobile Tools for Java (J2ME)
11
+ .mtj.tmp/
12
+
13
+ # Package Files #
14
+ *.jar
15
+ *.war
16
+ *.nar
17
+ *.ear
18
+ *.zip
19
+ *.tar.gz
20
+ *.rar
21
+
22
+ # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
23
+ hs_err_pid*
24
+
25
+ # VSCode files
26
+ *.code-workspace
27
+ .editorconfig
28
+
29
+ # Macintosh filesystem
30
+ .DS_Store
Inspect_Logic.py ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from io import StringIO
2
+ import streamlit as st
3
+
4
+ from smem_token_parser import SMEM_Parser, read_tokens_from_lines
5
+ from smem_obj import SMEM_Obj, ObjType
6
+
7
+ MIN_COLS = 4
8
+
9
+ # Set up page config before any other streamlit commands
10
+ st.set_page_config(page_title="Soar Agent Memory Inspector", layout="wide")
11
+ st.title("Logic Inspector")
12
+
13
+
14
+ def get_smem_root_from_file(smem_file):
15
+ # tokens = get_smem_tokens_from_local_file(smem_filename)
16
+ tokens = read_tokens_from_lines(smem_file)
17
+ if tokens == None:
18
+ st.error("Error reading file: '"+str(smem_file)+"'")
19
+ return None
20
+ parser = SMEM_Parser()
21
+ parser.parse_file(tokens)
22
+ return parser.get_context_root()
23
+
24
+ if "col_obj_list" not in st.session_state:
25
+ st.session_state["col_obj_list"] = None
26
+
27
+ file_upload_expander = st.expander(label="Select a file to inspect", expanded=(st.session_state.col_obj_list == None))
28
+ file = file_upload_expander.file_uploader(" ")
29
+
30
+ if file is not None:
31
+ if st.session_state.col_obj_list is None:
32
+ root = get_smem_root_from_file(StringIO(file.getvalue().decode("utf-8")))
33
+ if root:
34
+ st.session_state["col_obj_list"] = [None]*MIN_COLS
35
+ st.session_state.col_obj_list[0] = root
36
+ st.experimental_rerun()
37
+ else:
38
+ st.session_state["col_obj_list"] = None
39
+
40
+ # inpath = "/Users/bstearn1/Documents/Projects/OptumGitHub/Intelligent-Solutions/AICarePlan/agent/D-RULES_082222_smem.soar"
41
+ # inpath = "SMEM Generator/test_pim_smem.soar"
42
+ # st.session_state.col_obj_list[0] = get_smem_root_from_file(inpath)
43
+
44
+ if st.session_state.col_obj_list is None:
45
+ st.stop()
46
+
47
+ def add_col(index, obj):
48
+ st.session_state.col_obj_list = st.session_state.col_obj_list[:index+1]
49
+ st.session_state.col_obj_list.append(obj)
50
+ # st.session_state.current_tab_index = index+1
51
+ st.experimental_rerun()
52
+
53
+ def get_header_str(obj_type):
54
+ if obj_type == ObjType.CONTEXT:
55
+ return "START"
56
+ elif obj_type == ObjType.COND_CONJ:
57
+ return "CONDITION"
58
+ elif obj_type == ObjType.OP:
59
+ return "CHOICE"
60
+ elif obj_type == ObjType.ACT_GROUP:
61
+ return "RESULT"
62
+ else:
63
+ return " "
64
+
65
+ st.subheader("Click the buttons below to select conditions and to inspect resulting actions.")
66
+
67
+ cols = st.columns(max(MIN_COLS,len(st.session_state.col_obj_list)))
68
+
69
+ for i,col in enumerate(cols):
70
+ try:
71
+ if st.session_state.col_obj_list[i] == None:
72
+ break
73
+ except Exception as e:
74
+ # print("ERROR checking column "+str(i+1)+" of "+str(len(st.session_state.col_obj_list))+": "+str(e))
75
+ break
76
+ # Build the available objects to navigate under this col's object
77
+ obj = st.session_state.col_obj_list[i]
78
+ obj_str,obj_label,obj_desc = obj.to_string()
79
+ sub_objs = list(obj.get_child_objects(compact=True))
80
+ # print(obj.obj_type)
81
+ if len(sub_objs) > 0:
82
+ col.markdown("**"+get_header_str(sub_objs[0].obj_type)+"**",)
83
+ col.markdown("---")
84
+ # Show the object's main title and description
85
+ col.text(obj_str)
86
+ if obj_desc != None:
87
+ col.markdown("*"+obj_desc+"*")
88
+ for j,sub in enumerate(sub_objs):
89
+ sub_str,sub_label,sub_desc = sub.to_string()
90
+ if sub.obj_type == ObjType.OP:
91
+ subsub = list(sub.get_child_objects(compact=True))[0]
92
+ _,group_string,_ = subsub.to_string()
93
+ group_string = "\n * "+str(group_string).replace("AND", "\n * ")
94
+ col.markdown(group_string)
95
+ if col.button(sub_label, key="button"+str(i)+"-"+str(j)):
96
+ add_col(i,sub)
97
+ elif sub.obj_type == ObjType.ACT_GROUP:
98
+ col.markdown("*Output Details:* ")
99
+ col.markdown("\n * "+str(sub_str).replace(") AND", ")\n * "))
100
+ elif col.button(sub_label, key="button"+str(i)+"-"+str(j)):
101
+ add_col(i,sub)
README.md CHANGED
@@ -5,7 +5,7 @@ colorFrom: indigo
5
  colorTo: yellow
6
  sdk: streamlit
7
  sdk_version: 1.15.2
8
- app_file: app.py
9
  pinned: false
10
  ---
11
 
 
5
  colorTo: yellow
6
  sdk: streamlit
7
  sdk_version: 1.15.2
8
+ app_file: Inspect_Logic.py
9
  pinned: false
10
  ---
11
 
smem_obj.py ADDED
@@ -0,0 +1,276 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from enum import Enum
2
+
3
+ class SMEM_Obj():
4
+ def __init__(self):
5
+ self.processed = False
6
+ self.obj_type = None
7
+ self.id_var = None
8
+ self.class_str = None
9
+ self.wme_list = []
10
+ self.description = None
11
+
12
+ def set_id_var(self, var):
13
+ self.id_var = var
14
+
15
+ def add_wme(self, attr, val):
16
+ self.wme_list.append((attr,val))
17
+
18
+ def get_class(self):
19
+ for (attr,val) in self.wme_list:
20
+ if attr == "^class":
21
+ self.class_str = val
22
+ break
23
+ return self.class_str
24
+
25
+ def get_description(self):
26
+ if self.description != None:
27
+ return self.description
28
+ # Search for the description
29
+ for (attr,val) in self.wme_list:
30
+ if attr == "^description":
31
+ self.description = val
32
+ break
33
+ return self.description
34
+
35
+ def get_compact_printable_object(self):
36
+ """ Return this object if printable in compact mode, else return the set of next printable descendants.
37
+ """
38
+ if self.obj_type == None:
39
+ print("Warning: No obj_type for obj '"+self.id_var+"' in get_compact_printable_object().")
40
+ return None
41
+ retval = set()
42
+ returnSelf = False
43
+
44
+ if self.obj_type == ObjType.COND_PRIM:
45
+ for (attr,val) in self.wme_list:
46
+ if attr == "^supplies-condition":
47
+ val.obj_type = ObjType.COND_CONJ
48
+ retval.update(val.get_compact_printable_object())
49
+ elif self.obj_type == ObjType.COND_CONJ:
50
+ for (attr,val) in self.wme_list:
51
+ if attr == "^satisfies-op":
52
+ val.obj_type = ObjType.OP
53
+ returnSelf = True
54
+ break
55
+ elif attr == "^activates-context":
56
+ val.obj_type = ObjType.CONTEXT
57
+ returnSelf = True
58
+ break
59
+ elif attr == "^supplies-condition":
60
+ val.obj_type = ObjType.COND_CONJ
61
+ retval.update(val.get_compact_printable_object())
62
+ elif self.obj_type == ObjType.CONTEXT:
63
+ for (attr,val) in self.wme_list:
64
+ if attr == "^primes-condition":
65
+ val.obj_type = ObjType.COND_PRIM
66
+ retval.update(val.get_compact_printable_object())
67
+ else:
68
+ returnSelf = True
69
+
70
+ if returnSelf:
71
+ retval = set()
72
+ retval.add(self)
73
+
74
+ return retval
75
+
76
+ def get_child_objects(self, compact=True):
77
+ """ If compact=True, skip condition prims and contexts, only get conjunctions and action groups
78
+ """
79
+ if self.obj_type == None:
80
+ print("Warning: No obj_type for obj '"+self.id_var+"'")
81
+ return None
82
+ retval = set()
83
+ for (attr,val) in self.wme_list:
84
+ if self.obj_type == ObjType.CONTEXT:
85
+ if attr == "^primes-condition":
86
+ val.obj_type = ObjType.COND_PRIM
87
+ if compact:
88
+ retval.update(val.get_compact_printable_object())
89
+ else:
90
+ retval.add(val)
91
+ elif self.obj_type == ObjType.COND_PRIM:
92
+ if attr == "^supplies-condition":
93
+ val.obj_type = ObjType.COND_CONJ
94
+ if compact:
95
+ retval.update(val.get_compact_printable_object())
96
+ else:
97
+ retval.add(val)
98
+ elif self.obj_type == ObjType.COND_CONJ:
99
+ if attr == "^satisfies-op":
100
+ val.obj_type = ObjType.OP
101
+ retval.add(val)
102
+ elif attr == "^activates-context":
103
+ val.obj_type = ObjType.CONTEXT
104
+ if compact:
105
+ retval.update(val.get_compact_printable_object())
106
+ else:
107
+ retval.add(val)
108
+ elif attr == "^supplies-condition":
109
+ val.obj_type = ObjType.COND_CONJ
110
+ if compact:
111
+ retval.update(val.get_compact_printable_object())
112
+ else:
113
+ retval.add(val)
114
+ elif self.obj_type == ObjType.OP:
115
+ if attr == "^activates-action":
116
+ val.obj_type = ObjType.ACT_GROUP
117
+ retval.add(val)
118
+ elif self.obj_type == ObjType.ACT_GROUP:
119
+ if attr == "^action":
120
+ val.obj_type = ObjType.ACT_PRIM
121
+ retval.add(val)
122
+ return retval
123
+
124
+ def get_prim_cond_string(self, negate=False):
125
+ if len(self.wme_list) == 0:
126
+ return "", ""
127
+ if negate:
128
+ op_join_str = " is not "
129
+ else:
130
+ op_join_str = " is "
131
+ if self.class_str == None:
132
+ self.get_class()
133
+
134
+ retval_long, retval_short, retval_desc = ["(", "(", None]
135
+ if self.class_str == "prim":
136
+ group_str, attr_str, val_str = ["","",""]
137
+ for (attr,val) in self.wme_list:
138
+ if attr == "^group":
139
+ group_str = val
140
+ elif attr == "^attribute":
141
+ attr_str = val
142
+ elif attr == "^value":
143
+ val_str = val
144
+ elif attr == "^description":
145
+ self.description = val
146
+ retval_desc = val
147
+ retval_long += group_str+"."+attr_str+op_join_str+val_str
148
+ retval_short += attr_str+op_join_str+val_str
149
+ return retval_long+")", retval_short+")", retval_desc
150
+
151
+ def get_cond_string(self):
152
+ retval_long, retval_short, retval_desc = ["","",None]
153
+ # Compile the parent prim descriptions
154
+ parent_conds = []
155
+ # child_conjs = []
156
+ is_negation = (self.get_class() == "negation")
157
+ for (attr,val) in self.wme_list:
158
+ if attr == "^receives-condition":
159
+ parent_conds.append(val)
160
+ if attr == "^description":
161
+ self.description = val
162
+ retval_desc = val
163
+ # if attr == "^supplies-condition":
164
+ # child_conjs.append(val)
165
+
166
+ for i,cond in enumerate(parent_conds):
167
+ if i > 0:
168
+ retval_long += " \nAND "
169
+ retval_short += " AND "
170
+ if cond.obj_type == ObjType.COND_CONJ:
171
+ long, short, _ = cond.get_cond_string()
172
+ else:
173
+ long, short, _ = cond.get_prim_cond_string(negate=is_negation)
174
+ retval_long += long
175
+ retval_short += short
176
+ return retval_long, retval_short, retval_desc
177
+
178
+ def get_op_string(self):
179
+ retval_long, retval_short, retval_desc = ["", "", None]
180
+ numeric_pref = None
181
+ for (attr,val) in self.wme_list:
182
+ if attr == "^name":
183
+ retval_long = val
184
+ retval_short = val
185
+ elif attr == "^numeric-preference":
186
+ try:
187
+ numeric_pref = str(float(val)*100)+"%"
188
+ except Exception as e:
189
+ print("ERROR in get_op_string() converting numeric_pref to string: "+str(e))
190
+ numeric_pref = None
191
+ elif attr == "^description":
192
+ self.description = val
193
+ retval_desc = val
194
+ if numeric_pref:
195
+ retval_long += "\nPercentage: "+numeric_pref
196
+ # print(self.id_var+": "+retval)
197
+ return retval_long, retval_short, retval_desc
198
+
199
+ def get_prim_act_string(self):
200
+ if len(self.wme_list) == 0:
201
+ return ""
202
+ if self.class_str == None:
203
+ self.get_class()
204
+ retval_long = "("
205
+ retval_short = "("
206
+ if self.class_str == "prim":
207
+ type_str, attr_str, val_str = ["","",""]
208
+ for (attr,val) in self.wme_list:
209
+ if attr == "^type":
210
+ type_str = val
211
+ elif attr == "^attribute":
212
+ attr_str = val
213
+ elif attr == "^value":
214
+ val_str = val
215
+ retval_long += type_str+"."+attr_str+" = "+val_str
216
+ retval_short += attr_str+" = "+val_str
217
+ elif self.class_str == "prim-message":
218
+ type_str, flag_str, msg_str = ["","",""]
219
+ for (attr,val) in self.wme_list:
220
+ if attr == "^type":
221
+ type_str = val
222
+ elif attr == "^flag":
223
+ flag_str = val
224
+ elif attr == "^message":
225
+ msg_str = val
226
+ retval_long += type_str+".flag = "+flag_str+" AND "+type_str+'.message = "'+msg_str+'"'
227
+ retval_short += 'message: "'+msg_str+'"'
228
+ return retval_long+")", retval_short+")"
229
+
230
+ def get_act_group_string(self):
231
+ retval_long, retval_short, retval_desc = ["", "", None]
232
+ # Compile the child prim action descriptions
233
+ child_prims = []
234
+ for (attr,val) in self.wme_list:
235
+ if attr == "^action":
236
+ child_prims.append(val)
237
+ elif attr == "^description":
238
+ self.description = val
239
+ retval_desc = val
240
+ for i,prim in enumerate(child_prims):
241
+ if i > 0:
242
+ retval_long += " AND "
243
+ retval_short += " AND "
244
+ long, short = prim.get_prim_act_string()
245
+ retval_long += long
246
+ retval_short += short
247
+ return retval_long, retval_short, retval_desc
248
+
249
+ def to_string(self):
250
+ long = self.id_var
251
+ short = self.id_var
252
+ description = None
253
+
254
+ if self.obj_type == ObjType.CONTEXT:
255
+ return "", "", self.get_description()
256
+ if self.obj_type == ObjType.COND_PRIM:
257
+ long, short, description = self.get_prim_cond_string()
258
+ # long = "IF "+long
259
+ # short = "IF "+short
260
+ if self.obj_type == ObjType.COND_CONJ:
261
+ long, short, description = self.get_cond_string()
262
+ if self.obj_type == ObjType.OP:
263
+ long, short, description = self.get_op_string()
264
+ if self.obj_type == ObjType.ACT_GROUP:
265
+ long, short, description = self.get_act_group_string()
266
+ if self.obj_type == ObjType.ACT_PRIM:
267
+ long, short = self.get_prim_act_string()
268
+ return long, short, description
269
+
270
+ class ObjType(Enum):
271
+ CONTEXT = 0
272
+ COND_PRIM = 1
273
+ COND_CONJ = 2
274
+ OP = 3
275
+ ACT_GROUP = 4
276
+ ACT_PRIM = 5
smem_token_parser.py ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ from os import access, R_OK
3
+ from os.path import isfile
4
+ from collections import defaultdict
5
+ import re
6
+
7
+ from smem_obj import SMEM_Obj, ObjType
8
+
9
+
10
+ def clean_smem_string_token(s):
11
+ return str(s).replace("|","")
12
+
13
+ def removeComments(s):
14
+ """ Trim any '#' content from the given string
15
+ """
16
+ ind = str(s).find('#')
17
+ if (ind == -1):
18
+ return s
19
+ return s[:ind]
20
+
21
+ def read_tokens_from_lines(file):
22
+ retval = [] # A list of all tokens from the "smem --add {}" contents.
23
+ inSmemAdd = False # Whether the read is inside an "smem --add {}" command
24
+
25
+ try:
26
+ for line in file:
27
+ sline = line.lstrip()
28
+ # Don't use comments
29
+ if sline.startswith('#'):
30
+ continue
31
+ # Check if we're in an smem --add command
32
+ if not inSmemAdd:
33
+ if sline.startswith("smem --add {"):
34
+ inSmemAdd = True
35
+ # Only add from this line any content after the opening '{'
36
+ sline = sline[12:]
37
+ else:
38
+ continue
39
+
40
+ # Get the tokens: split on whitespace, pipe quotes, parentheses, and brackets, but only exclude whitespace delimiters and pipe quotes
41
+ regex_pattern = '[\s+]|\\|(.+?)\\||(?<=\\))|(?<=\\()|(?<=\\{)|(?<=\\})'
42
+ tokens = re.split(regex_pattern, removeComments(sline).rstrip())
43
+
44
+ while None in tokens:
45
+ tokens.remove(None)
46
+ while '' in tokens:
47
+ tokens.remove('')
48
+
49
+ # Check for the closing character
50
+ try:
51
+ ind = tokens.index('}')
52
+ # If no exception, it was found
53
+ tokens = tokens[:ind]
54
+ inSmemAdd = False
55
+ except:
56
+ pass
57
+
58
+ # Add this line to the return list
59
+ if len(tokens) > 0:
60
+ retval.extend(tokens)
61
+ except Exception as e:
62
+ print("ERROR extracting tokens from the given file: "+str(e), file=sys.stderr)
63
+ return None
64
+
65
+ return retval
66
+
67
+ """
68
+ This method scans a file that holds an 'smem --add{}' command and returns the relevant tokens from that file
69
+ """
70
+ def get_smem_tokens_from_local_file(filename):
71
+ # Error check for reading the file
72
+ if not isfile(filename):
73
+ print("ERROR in get_smem_tokens_from_local_file(): File does not exist: '"+str(filename)+"'.", file=sys.stderr)
74
+ return None
75
+ if not access(filename, R_OK):
76
+ print("ERROR in get_smem_tokens_from_local_file(): File is not readable: '"+str(filename)+"'.", file=sys.stderr)
77
+ return None
78
+
79
+ # Get the file content
80
+ with open(filename) as file:
81
+ retval = read_tokens_from_lines(file)
82
+
83
+ # All done
84
+ return retval
85
+
86
+
87
+ class SMEM_Parser():
88
+
89
+ def __init__(self):
90
+ # Init the data structured needed to parse the smem file
91
+ self.smem_var_obj_map = defaultdict(SMEM_Obj)
92
+
93
+ def parse_file(self, smem_tokens):
94
+ # Iterate through the tokens
95
+ current_obj = None
96
+ current_attr = None
97
+ isNextWMEId = False
98
+ for token in smem_tokens:
99
+ # Skip empty tokens
100
+ if len(token) == 0:
101
+ continue
102
+ # Get WME ID
103
+ if isNextWMEId:
104
+ current_obj = self.smem_var_obj_map[token]
105
+ current_obj.set_id_var(token)
106
+ isNextWMEId = False
107
+ continue
108
+ # Get start of obj
109
+ if token == '(':
110
+ isNextWMEId = True
111
+ continue
112
+ # Get end of obj
113
+ if token == ')':
114
+ current_obj = None
115
+ current_attr = None
116
+ continue
117
+ # Get attributes
118
+ if token.startswith('^'):
119
+ current_attr = token
120
+ continue
121
+ # Get values
122
+ if current_obj != None and current_attr != None:
123
+ # Add the WME to the current object
124
+ if token.startswith('<'):
125
+ token_val = self.smem_var_obj_map[token]
126
+ token_val.set_id_var(token)
127
+ else:
128
+ token_val = clean_smem_string_token(token)
129
+ current_obj.add_wme(current_attr, token_val)
130
+ else:
131
+ print("ERROR: Unexpected token '"+token+"'.")
132
+ return
133
+
134
+ def get_context_root(self):
135
+ for var,obj in self.smem_var_obj_map.items():
136
+ for (attr,val) in obj.wme_list:
137
+ if attr == "^context-root":
138
+ obj.obj_type = ObjType.CONTEXT
139
+ return obj
140
+ return None