1818MA 02110-1301, USA.
1919"""
2020
21+ import re
22+
2123
2224class SlycotError (RuntimeError ):
2325 """Slycot exception base class"""
@@ -36,64 +38,153 @@ class SlycotParameterError(SlycotError, ValueError):
3638
3739 pass
3840
41+
3942class SlycotArithmeticError (SlycotError , ArithmeticError ):
4043 """A Slycot computation failed"""
4144
4245 pass
4346
44- def filter_docstring_exceptions (docstring ):
45- """Check a docstring to find exception descriptions"""
46-
47- # check-count the message indices
48- index = 0
49- exdict = {}
50- msg = []
51- for l in docstring .split ('\n ' ):
52- l = l .strip ()
53- if l [:10 ] == ":e.info = " and l [- 1 ] == ":" :
54- try :
55- idx = int (l [10 :- 1 ])
56- if msg :
57- exdict [index ] = '\n ' .join (msg )
58- msg = []
59- index = idx
60- except ValueError :
61- if msg :
62- exdict [index ] = '\n ' .join (msg )
63- msg = []
64- index = 0
65- elif not l :
66- if msg :
67- exdict [index ] = '\n ' .join (msg )
68- msg = []
69- index = 0
70- elif index :
71- msg .append (l .strip ())
72- if msg :
73- exdict [index ] = '\n ' .join (msg )
74- return exdict
75-
76- def raise_if_slycot_error (info , arg_list , docstring = None ):
77- """Raise exceptions if slycot info returned is non-zero
78-
79- For negative info, the argument as indicated in arg_list was erroneous
80-
81- For positive info, the matching exception text is recovered from
82- the docstring, which may in many cases simply be the python interface
83- routine docstring
47+
48+ def raise_if_slycot_error (info , arg_list = None , docstring = None , checkvars = {}):
49+ """Raise exceptions if slycot info returned is non-zero.
50+
51+ Parameters
52+ ----------
53+ info: int
54+ The parameter INFO returned by the SLICOT subroutine
55+ arg_list: list of str, optional
56+ A list of arguments (possibly hidden by the wrapper) of the SLICOT
57+ subroutine
58+ docstring: str, optional
59+ The docstring of the Slycot function
60+ checkvars: dict, optional
61+ dict of variables for evaluation of <infospec> and formatting the
62+ exception message
63+
64+ Notes
65+ -----
66+ If the numpydoc compliant docstring has a "Raises" section with one or
67+ multiple definition terms ``SlycotError : e`` or a subclass of it,
68+ the matching exception text is used.
69+
70+ The definition body must contain a reST compliant field list with
71+ ':<infospec>:' as field name, where <infospec> specifies the valid values
72+ for `e.ìnfo` in a python parseable expression using the variables provided
73+ in `checkvars`. A single " = " is treated as " == ".
74+
75+ The body of the field list contains the exception message and can contain
76+ replacement fields in format string syntax using the variables in
77+ `checkvars`.
78+
79+ For negative info, the argument as indicated in arg_list was erroneous and
80+ a generic SlycotParameterError is raised if no custom text was defined in
81+ the docstring or no docstring is provided.
82+
83+ Example
84+ -------
85+ >>> def fun(info):
86+ ... \" ""Example function
87+ ...
88+ ... Raises
89+ ... ------
90+ ... SlycotArithmeticError : e
91+ ... :e.info = 1: Info is 1
92+ ... :e.info > 1 and e.info < n:
93+ ... Info is {e.info}, which is between 1 and {n}
94+ ... :n <= e.info < m:
95+ ... {e.info} is between {n} and {m:10.2g}!
96+ ... \" ""
97+ ... n, m = 4, 1.2e2
98+ ... raise_if_slycot_error(info,
99+ ... arg_list=["a", "b", "c"],
100+ ... docstring=fun.__doc__,
101+ ... checkvars=locals())
102+ ...
103+ >>> fun(0)
104+ >>> fun(-1)
105+ SlycotParameterError: The following argument had an illegal value: a
106+ >>> fun(1)
107+ SlycotArithmeticError: Info is 1
108+ >>> fun(2)
109+ SlycotArithmeticError: Info is 2, which is between 1 and 4
110+ >>> fun(5)
111+ SlycotArithmeticError: 4 is between 4 and 1.2e+02!
84112 """
113+ if docstring :
114+ slycot_error_map = {"SlycotError" : SlycotError ,
115+ "SlycotParameterError" : SlycotParameterError ,
116+ "SlycotArithmeticError" : SlycotArithmeticError }
85117
86- if info < 0 :
118+ docline = iter (docstring .splitlines ())
119+ info_eval = False
120+ try :
121+ while "Raises" not in next (docline ):
122+ continue
123+
124+ section_indent = next (docline ).index ("-" )
125+
126+ slycot_error = None
127+ for l in docline :
128+ print (l )
129+ # ignore blank lines
130+ if not l .strip ():
131+ continue
132+
133+
134+ # reached end of Raises section without match
135+ if not l [:section_indent ].isspace ():
136+ return None
137+
138+ # Exception Type
139+ ematch = re .match (
140+ r'(\s*)(Slycot(Parameter|Arithmetic)?Error) : e' , l )
141+ if ematch :
142+ error_indent = len (ematch [1 ])
143+ slycot_error = ematch [2 ]
144+
145+ # new infospec
146+ if slycot_error :
147+ imatch = re .match (
148+ r'(\s{' + str (error_indent + 1 ) + r',}):(.*):\s*(.*)' ,
149+ l )
150+ if imatch :
151+ infospec_indent = len (imatch [1 ])
152+ infospec = imatch [2 ]
153+ # Don't handle the standard case unless we have i
154+ if infospec == "e.info = -i" :
155+ if 'i' not in checkvars .keys ():
156+ continue
157+ infospec_ = infospec .replace (" = " , " == " )
158+ checkvars ['e' ] = SlycotError ("" , info )
159+ try :
160+ info_eval = eval (infospec_ , checkvars )
161+ except NameError :
162+ raise RuntimeError ("Unknown variable in infospec: "
163+ + infospec )
164+ except SyntaxError :
165+ raise RuntimeError ("Invalid infospec: "
166+ + infospec )
167+ if info_eval :
168+ message = imatch [3 ].strip () + '\n '
169+ mmatch = re .match (
170+ r'(\s{' + str (infospec_indent + 1 ) + r',})(.*)' ,
171+ next (docline ))
172+ if not mmatch :
173+ break # docstring
174+ body_indent = len (mmatch [1 ])
175+ message += mmatch [2 ] + '\n '
176+ for l in docline :
177+ if l and not l [:body_indent ].isspace ():
178+ break # message body
179+ message += l [body_indent :] + '\n '
180+ break # docstring
181+ except StopIteration :
182+ pass
183+ if info_eval and message :
184+ fmessage = '\n ' + message .format (** checkvars ).strip ()
185+ raise slycot_error_map [slycot_error ](fmessage , info )
186+
187+ if info < 0 and arg_list :
87188 message = ("The following argument had an illegal value: {}"
88- "" .format (arg_list [- info - 1 ]))
189+ "" .format (arg_list [- info - 1 ]))
89190 raise SlycotParameterError (message , info )
90- elif info > 0 and docstring :
91- # process the docstring for the error message
92- messages = filter_docstring_exceptions (docstring )
93- try :
94- raise SlycotParameterError (messages [info ], info )
95- except IndexError :
96- raise SlycotParameterError (
97- "Slycot returned an unhandled error code {}" .format (info ),
98- info )
99-
0 commit comments