@@ -76,8 +76,12 @@ class RE(object):
7676rs_version = 2.0
7777
7878# Exportable constants.
79- RS_ERR_MATCH = "ERR: No Reply Matched"
80- RS_ERR_REPLY = "ERR: No Reply Found"
79+ RS_ERR_MATCH = "[ERR: No reply matched]"
80+ RS_ERR_REPLY = "[ERR: No reply found]"
81+ RS_ERR_DEEP_RECURSION = "[ERR: Deep recursion detected]"
82+ RS_ERR_OBJECT = "[ERR: Error when executing Python object]"
83+ RS_ERR_OBJECT_HANDLER = "[ERR: No Object Handler]"
84+ RS_ERR_OBJECT_MISSING = "[ERR: Object Not Found]"
8185
8286
8387class RiveScript (object ):
@@ -1511,7 +1515,7 @@ def current_user(self):
15111515 # Reply Fetching Methods #
15121516 ############################################################################
15131517
1514- def reply (self , user , msg ):
1518+ def reply (self , user , msg , errors_as_replies = True ):
15151519 """Fetch a reply from the RiveScript brain."""
15161520 self ._say ("Get reply to [" + user + "] " + msg )
15171521
@@ -1525,20 +1529,30 @@ def reply(self, user, msg):
15251529
15261530 # If the BEGIN block exists, consult it first.
15271531 if "__begin__" in self ._topics :
1528- begin = self ._getreply (user , 'request' , context = 'begin' )
1532+ begin = self ._getreply (user , 'request' , context = 'begin' , ignore_object_errors = errors_as_replies )
15291533
15301534 # Okay to continue?
15311535 if '{ok}' in begin :
1532- reply = self ._getreply (user , msg )
1536+ try :
1537+ reply = self ._getreply (user , msg , ignore_object_errors = errors_as_replies )
1538+ except RiveScriptError as e :
1539+ if not errors_as_replies :
1540+ raise
1541+ reply = e .error_message
15331542 begin = begin .replace ('{ok}' , reply )
15341543
15351544 reply = begin
15361545
15371546 # Run more tag substitutions.
1538- reply = self ._process_tags (user , msg , reply )
1547+ reply = self ._process_tags (user , msg , reply , ignore_object_errors = errors_as_replies )
15391548 else :
15401549 # Just continue then.
1541- reply = self ._getreply (user , msg )
1550+ try :
1551+ reply = self ._getreply (user , msg , ignore_object_errors = errors_as_replies )
1552+ except RiveScriptError as e :
1553+ if not errors_as_replies :
1554+ raise
1555+ reply = e .error_message
15421556
15431557 # Save their reply history.
15441558 oldInput = self ._users [user ]['__history__' ]['input' ][:8 ]
@@ -1580,10 +1594,10 @@ def _format_message(self, msg, botreply=False):
15801594
15811595 return msg
15821596
1583- def _getreply (self , user , msg , context = 'normal' , step = 0 ):
1597+ def _getreply (self , user , msg , context = 'normal' , step = 0 , ignore_object_errors = True ):
15841598 # Needed to sort replies?
15851599 if 'topics' not in self ._sorted :
1586- raise Exception ("You forgot to call sort_replies()! " )
1600+ raise RepliesNotSortedError ("You must call sort_replies() once you are done loading RiveScript documents " )
15871601
15881602 # Initialize the user's profile?
15891603 if user not in self ._users :
@@ -1602,7 +1616,7 @@ def _getreply(self, user, msg, context='normal', step=0):
16021616
16031617 # Avoid deep recursion.
16041618 if step > self ._depth :
1605- return "ERR: Deep Recursion Detected"
1619+ raise DeepRecursionError
16061620
16071621 # Are we in the BEGIN statement?
16081622 if context == 'begin' :
@@ -1627,7 +1641,7 @@ def _getreply(self, user, msg, context='normal', step=0):
16271641 if topic not in self ._topics :
16281642 # This was handled before, which would mean topic=random and
16291643 # it doesn't exist. Serious issue!
1630- return "[ERR: No default topic 'random' was found!]"
1644+ raise NoDefaultRandomTopicError ( "no default topic 'random' was found" )
16311645
16321646 # Create a pointer for the matched data when we find it.
16331647 matched = None
@@ -1743,9 +1757,10 @@ def _getreply(self, user, msg, context='normal', step=0):
17431757 # See if there are any hard redirects.
17441758 if matched ["redirect" ]:
17451759 self ._say ("Redirecting us to " + matched ["redirect" ])
1746- redirect = self ._process_tags (user , msg , matched ["redirect" ], stars , thatstars , step )
1760+ redirect = self ._process_tags (user , msg , matched ["redirect" ], stars , thatstars , step ,
1761+ ignore_object_errors )
17471762 self ._say ("Pretend user said: " + redirect )
1748- reply = self ._getreply (user , redirect , step = (step + 1 ))
1763+ reply = self ._getreply (user , redirect , step = (step + 1 ), ignore_object_errors = ignore_object_errors )
17491764 break
17501765
17511766 # Check the conditionals.
@@ -1761,8 +1776,8 @@ def _getreply(self, user, msg, context='normal', step=0):
17611776 self ._say ("Left: " + left + "; eq: " + eq + "; right: " + right + " => " + potreply )
17621777
17631778 # Process tags all around.
1764- left = self ._process_tags (user , msg , left , stars , thatstars , step )
1765- right = self ._process_tags (user , msg , right , stars , thatstars , step )
1779+ left = self ._process_tags (user , msg , left , stars , thatstars , step , ignore_object_errors )
1780+ right = self ._process_tags (user , msg , right , stars , thatstars , step , ignore_object_errors )
17661781
17671782 # Defaults?
17681783 if len (left ) == 0 :
@@ -1828,9 +1843,9 @@ def _getreply(self, user, msg, context='normal', step=0):
18281843
18291844 # Still no reply?
18301845 if not foundMatch :
1831- reply = RS_ERR_MATCH
1846+ raise NoMatchError
18321847 elif len (reply ) == 0 :
1833- reply = RS_ERR_REPLY
1848+ raise NoReplyError
18341849
18351850 self ._say ("Reply: " + reply )
18361851
@@ -1851,7 +1866,7 @@ def _getreply(self, user, msg, context='normal', step=0):
18511866 reply = reply .replace ('<set {key}={value}>' .format (key = match [0 ], value = match [1 ]), '' )
18521867 else :
18531868 # Process more tags if not in BEGIN.
1854- reply = self ._process_tags (user , msg , reply , stars , thatstars , step )
1869+ reply = self ._process_tags (user , msg , reply , stars , thatstars , step , ignore_object_errors )
18551870
18561871 return reply
18571872
@@ -1860,9 +1875,9 @@ def _substitute(self, msg, kind):
18601875
18611876 # Safety checking.
18621877 if 'lists' not in self ._sorted :
1863- raise Exception ("You forgot to call sort_replies()! " )
1878+ raise RepliesNotSortedError ("You must call sort_replies() once you are done loading RiveScript documents " )
18641879 if kind not in self ._sorted ["lists" ]:
1865- raise Exception ("You forgot to call sort_replies()! " )
1880+ raise RepliesNotSortedError ("You must call sort_replies() once you are done loading RiveScript documents " )
18661881
18671882 # Get the substitution map.
18681883 subs = None
@@ -2008,7 +2023,7 @@ def _precompile_regexp(self, trigger):
20082023
20092024 self ._regexc ["trigger" ][trigger ] = self ._reply_regexp (None , trigger )
20102025
2011- def _process_tags (self , user , msg , reply , st = [], bst = [], depth = 0 ):
2026+ def _process_tags (self , user , msg , reply , st = [], bst = [], depth = 0 , ignore_object_errors = True ):
20122027 """Post process tags in a message."""
20132028 stars = ['' ]
20142029 stars .extend (st )
@@ -2197,11 +2212,21 @@ def _process_tags(self, user, msg, reply, st=[], bst=[], depth=0):
21972212 lang = self ._objlangs [obj ]
21982213 if lang in self ._handlers :
21992214 # We do.
2200- output = self ._handlers [lang ].call (self , obj , user , args )
2215+ try :
2216+ output = self ._handlers [lang ].call (self , obj , user , args )
2217+ except python .PythonObjectError as e :
2218+ self ._warn (str (e ))
2219+ if not ignore_object_errors :
2220+ raise ObjectError (str (e ))
2221+ output = RS_ERR_OBJECT
22012222 else :
2202- output = '[ERR: No Object Handler]'
2223+ if not ignore_object_errors :
2224+ raise ObjectError (RS_ERR_OBJECT_HANDLER )
2225+ output = RS_ERR_OBJECT_HANDLER
22032226 else :
2204- output = '[ERR: Object Not Found]'
2227+ if not ignore_object_errors :
2228+ raise ObjectError (RS_ERR_OBJECT_MISSING )
2229+ output = RS_ERR_OBJECT_MISSING
22052230
22062231 reply = reply .replace ('<call>{match}</call>' .format (match = match ), output )
22072232
@@ -2431,6 +2456,52 @@ def _dump(self):
24312456 print ("=== Syntax Tree ===" )
24322457 pp .pprint (self ._syntax )
24332458
2459+
2460+ ################################################################################
2461+ # Exception Classes #
2462+ ################################################################################
2463+
2464+ class RiveScriptError (Exception ):
2465+ """RiveScript base exception class"""
2466+ def __init__ (self , error_message = None ):
2467+ super (RiveScriptError , self ).__init__ (error_message )
2468+ self .error_message = error_message
2469+
2470+
2471+ class NoMatchError (RiveScriptError ):
2472+ """No reply could be matched"""
2473+ def __init__ (self ):
2474+ super (NoMatchError , self ).__init__ (RS_ERR_MATCH )
2475+
2476+
2477+ class NoReplyError (RiveScriptError ):
2478+ """No reply could be found"""
2479+ def __init__ (self ):
2480+ super (NoReplyError , self ).__init__ (RS_ERR_REPLY )
2481+
2482+
2483+ class ObjectError (RiveScriptError ):
2484+ """An error occurred when executing a Python object"""
2485+ def __init__ (self , error_message = RS_ERR_OBJECT ):
2486+ super (ObjectError , self ).__init__ (error_message )
2487+
2488+
2489+ class DeepRecursionError (RiveScriptError ):
2490+ """Prevented an infinite loop / deep recursion, unable to retrieve a reply for this message"""
2491+ def __init__ (self ):
2492+ super (DeepRecursionError , self ).__init__ (RS_ERR_DEEP_RECURSION )
2493+
2494+
2495+ class NoDefaultRandomTopicError (Exception ):
2496+ """No default topic 'random' could be found, critical error"""
2497+ pass
2498+
2499+
2500+ class RepliesNotSortedError (Exception ):
2501+ """sort_replies() was not called after the RiveScript documents were loaded, critical error"""
2502+ pass
2503+
2504+
24342505################################################################################
24352506# Interactive Mode #
24362507################################################################################
0 commit comments