1818# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1919# SOFTWARE.
2020
21- from typing import Any , Iterator , List , Tuple , Union
21+ from typing import Any , Iterator , List , Tuple , Union , NamedTuple , TypeVar , Generic
2222
2323from collections import namedtuple , defaultdict
2424from shlex import quote
@@ -144,16 +144,24 @@ def __init__(self, _s, pos, col_offset):
144144 super ().__init__ ()
145145 self .pos , self .col_offset = pos , col_offset
146146
147- class PosView (bytes ):
147+ class View (bytes ):
148+ def __getitem__ (self , key ):
149+ return memoryview (self ).__getitem__ (key )
150+
151+ def __init__ (self , s ):
152+ super ().__init__ ()
153+ self .s = s
154+
155+ class PosView (View ):
148156 NL = b"\n "
149157
150158 def __new__ (cls , s ):
151159 bs = s .encode ("utf-8" )
152160 # https://stackoverflow.com/questions/20221858/
153- return super ().__new__ (cls , bs ) if isinstance (s , PosStr ) else memoryview (bs )
161+ return super ().__new__ (cls , bs ) if isinstance (s , PosStr ) else View (bs )
154162
155163 def __init__ (self , s ):
156- super ().__init__ ()
164+ super ().__init__ (s )
157165 self .pos , self .col_offset = s .pos , s .col_offset
158166
159167 def __getitem__ (self , key ):
@@ -183,6 +191,27 @@ def translate_span(self, beg, end):
183191 return Range (self .translate_offset (beg ),
184192 self .translate_offset (end ))
185193
194+ _T = TypeVar ("_T" )
195+ class Notification (NamedTuple , Generic [_T ]): # type: ignore
196+ obj : _T
197+ message : str
198+ location : Range
199+ level : int
200+
201+ class Observer :
202+ def _notify (self , n : Notification ):
203+ raise NotImplementedError ()
204+
205+ def notify (self , obj , message , location , level ):
206+ self ._notify (Notification (obj , message , location , level ))
207+
208+ class StderrObserver (Observer ):
209+ def _notify (self , n : Notification ):
210+ header = n .location .as_header () if n .location else "!!"
211+ message = n .message .rstrip ().replace ("\n " , "\n " )
212+ level_name = {2 : "WARNING" , 3 : "ERROR" }.get (n .level , "??" )
213+ stderr .write ("{} ({}/{}) {}\n " .format (header , level_name , n .level , message ))
214+
186215PrettyPrinted = namedtuple ("PrettyPrinted" , "sid pp" )
187216
188217def sexp_hd (sexp ):
@@ -221,12 +250,13 @@ def version_info(sertop_bin=SERTOP_BIN):
221250 def __init__ (self , args = (), # pylint: disable=dangerous-default-value
222251 sertop_bin = SERTOP_BIN ,
223252 pp_args = DEFAULT_PP_ARGS ):
224- """Configure and start a ``sertop`` instance."""
253+ """Configure a ``sertop`` instance."""
225254 self .args , self .sertop_bin = [* args , * SerAPI .DEFAULT_ARGS ], sertop_bin
226255 self .sertop = None
227256 self .next_qid = 0
228257 self .pp_args = {** SerAPI .DEFAULT_PP_ARGS , ** pp_args }
229258 self .last_response = None
259+ self .observer : Observer = StderrObserver ()
230260
231261 def __enter__ (self ):
232262 self .reset ()
@@ -375,15 +405,7 @@ def _clip_span(loc, chunk):
375405 def _range_of_span (span , chunk ):
376406 return chunk .translate_span (* span ) if isinstance (chunk , PosView ) else None
377407
378- @staticmethod
379- def _print_warning (msg , loc ):
380- header = loc .as_header () if loc else "!!"
381- msg = msg .rstrip ().replace ("\n " , "\n " )
382- stderr .write ("{} (WARNING/2) {}\n " .format (header , msg ))
383- # FIXME report this error to calling context
384-
385- @staticmethod
386- def _warn_on_exn (response , chunk ):
408+ def _warn_on_exn (self , response , chunk ):
387409 QUOTE = ' > '
388410 ERR_FMT = "Coq raised an exception:\n {}"
389411 msg = sx .tostr (response .exn )
@@ -392,7 +414,7 @@ def _warn_on_exn(response, chunk):
392414 if chunk :
393415 err += "\n " + SerAPI ._highlight_exn (span , chunk , prefix = QUOTE )
394416 err += "\n " + "Results past this point may be unreliable."
395- SerAPI . _print_warning ( err , SerAPI ._range_of_span (span , chunk ))
417+ self . observer . notify ( chunk . s , err , SerAPI ._range_of_span (span , chunk ), level = 3 )
396418
397419 def _collect_messages (self , typs : Tuple [type , ...], chunk , sid ) -> Iterator [Any ]:
398420 warn_on_exn = ApiExn not in typs
@@ -404,7 +426,7 @@ def _collect_messages(self, typs: Tuple[type, ...], chunk, sid) -> Iterator[Any]
404426 return
405427 if warn_on_exn and isinstance (response , ApiExn ):
406428 if sid is None or response .sids is None or sid in response .sids :
407- SerAPI ._warn_on_exn (response , chunk )
429+ self ._warn_on_exn (response , chunk )
408430 if (not typs ) or isinstance (response , typs ): # type: ignore
409431 yield response
410432
@@ -498,11 +520,16 @@ def run(self, chunk):
498520 if fragment is None :
499521 err = "Orphaned message for sid {}:" .format (message .sid )
500522 err += "\n " + indent (message .pp , " > " )
501- SerAPI ._print_warning (err , SerAPI ._range_of_span ((0 , len (chunk )), chunk ))
523+ err_range = SerAPI ._range_of_span ((0 , len (chunk )), chunk )
524+ self .observer .notify (chunk .s , err , err_range , level = 2 )
502525 else :
503526 fragment .messages .append (Message (message .pp ))
504527 return fragments
505528
529+ def annotate (self , chunks ):
530+ with self as api :
531+ return [api .run (chunk ) for chunk in chunks ]
532+
506533def annotate (chunks , sertop_args = ()):
507534 r"""Annotate multiple `chunks` of Coq code.
508535
@@ -514,5 +541,4 @@ def annotate(chunks, sertop_args=()):
514541 >>> annotate(["Check 1."])
515542 [[Sentence(contents='Check 1.', messages=[Message(contents='1\n : nat')], goals=[])]]
516543 """
517- with SerAPI (args = sertop_args ) as api :
518- return [api .run (chunk ) for chunk in chunks ]
544+ return SerAPI (args = sertop_args ).annotate (chunks )
0 commit comments