@@ -338,34 +338,54 @@ def coq_partition(doc):
338338COQDOC_CLOSE = re .compile (r"[ \t]*[*]+[)]\Z" )
339339
340340DEFAULT_HEADER = ".. coq::"
341- DIRECTIVE = re .compile (r"([ \t]*)([.][.] coq::.*)?" )
341+ INDENT = re .compile (r"(?P<indent>[ \t]*)" )
342+ COQ_DIRECTIVE = re .compile (r"(?P<indent>[ \t]*)([.][.] coq::.*)" )
342343
343- Lit = namedtuple ("Lit" , "lines directive indent" )
344+ Lit = namedtuple ("Lit" , "lines directive_lines indent" )
344345CodeBlock = namedtuple ("CodeBlock" , "lines indent" )
345346
347+ def _last_coq_directive (lines ):
348+ directive = deque ()
349+ expected_coq_indent = float ("+inf" )
350+ while lines :
351+ line = lines .pop ()
352+ directive .appendleft (line )
353+ indent = measure_indentation (line )
354+ m = line .match (COQ_DIRECTIVE )
355+ if m :
356+ if indent <= expected_coq_indent :
357+ return lines , m , directive
358+ break
359+ if not line .isspace ():
360+ expected_coq_indent = min (expected_coq_indent , indent - 3 )
361+ if expected_coq_indent < 0 :
362+ break # No need to keep looping
363+ return lines + directive , None , None
364+
346365def lit (lines , indent ):
347366 strip_deque (lines )
348- m = lines and lines [- 1 ].match (DIRECTIVE )
349- indent , directive = m .groups () if m else (indent , None )
350- if directive :
351- directive = lines .pop ()
367+ lines , m , directive_lines = _last_coq_directive (lines )
368+ if directive_lines :
369+ indent = m .group ("indent" )
352370 strip_deque (lines )
353371 else :
354- directive = indent + DEFAULT_HEADER
355- return Lit (lines , directive = directive , indent = indent )
372+ if lines :
373+ indent = lines [- 1 ].match (INDENT ).group ("indent" )
374+ directive_lines = [indent + DEFAULT_HEADER ]
375+ return Lit (lines , directive_lines = directive_lines , indent = indent )
356376
357377def number_lines (span , start ):
358378 lines = span .split ("\n " )
359379 d = deque (Line (num , [s ]) for (num , s ) in enumerate (lines , start = start ))
360380 return start + len (lines ) - 1 , d
361381
362382def gen_rst (spans ):
363- linum , indent , prefix = 0 , "" , DEFAULT_HEADER
383+ linum , indent , prefix = 0 , "" , [ DEFAULT_HEADER ]
364384 for span in spans :
365385 if isinstance (span , Comment ):
366386 linum , lines = number_lines (span .v .trim (LIT_OPEN , LIT_CLOSE ), linum )
367387 litspan = lit (lines , indent )
368- indent , prefix = litspan .indent , litspan .directive
388+ indent , prefix = litspan .indent , litspan .directive_lines
369389 if litspan .lines :
370390 yield from (replace (l , UNQUOTE_PAIRS ) for l in litspan .lines )
371391 yield ""
@@ -375,7 +395,7 @@ def gen_rst(spans):
375395 for l in lines :
376396 l .parts .insert (0 , indent + " " )
377397 if lines :
378- yield prefix
398+ yield from prefix
379399 yield ""
380400 yield from lines
381401 yield ""
@@ -399,6 +419,40 @@ def coq2rst_lines(coq):
399419 return gen_rst (coq_partition_literate (coq ))
400420
401421def coq2rst (coq ):
422+ """Translate a fragment of Coq code to reST.
423+
424+ >>> print(coq2rst('''
425+ ... (*|
426+ ... Example:
427+ ... |*)
428+ ...
429+ ... Goal True.
430+ ...
431+ ... (*|
432+ ... Second example:
433+ ...
434+ ... .. coq::
435+ ... :name:
436+ ... snd
437+ ... |*)
438+ ...
439+ ... exact I. Qed.
440+ ... '''))
441+ Example:
442+ <BLANKLINE>
443+ .. coq::
444+ <BLANKLINE>
445+ Goal True.
446+ <BLANKLINE>
447+ Second example:
448+ <BLANKLINE>
449+ .. coq::
450+ :name:
451+ snd
452+ <BLANKLINE>
453+ exact I. Qed.
454+ <BLANKLINE>
455+ """
402456 return join_lines (coq2rst_lines (coq ))
403457
404458def coq2rst_marked (coq , point , marker ):
@@ -417,31 +471,68 @@ def coq2rst_marked(coq, point, marker):
417471# note that it detects *all* ‘.. coq::’ blocks, including quoted ones.
418472
419473COQ_BLOCK = re .compile (r"""
420- ^(?P<indent>[ \t]*)[.][.][ ]coq::.*
474+ (?P<directive>
475+ ^(?P<indent>[ \t]*)
476+ [.][.][ ]coq::.*
477+ (?P<options>
478+ (?:\n
479+ (?P=indent)[ ][ ][ ] [ \t]*[^ \t].*$)*))
421480(?P<body>
422- (?:\n
423- (?:[ \t]*\n)*
424- (?P=indent)[ ][ ][ ].*$)*)
481+ (?:\n
482+ (?:[ \t]*\n)*
483+ (?P=indent)[ ][ ][ ] .*$)*)
425484""" , re .VERBOSE | re .MULTILINE )
426485
427486def rst_partition (s ):
487+ """Identify ``.. coq::`` blocks in reST sources.
488+
489+ >>> import black
490+ >>> print(black.format_str(repr(list(rst_partition('''\\
491+ ... .. coq::
492+ ...
493+ ... Goal True.
494+ ... exact I. Qed.\\
495+ ... '''))), mode=black.FileMode()))
496+ [
497+ Lit(
498+ lines=deque([Line(num=0, parts=[""])]),
499+ directive_lines=deque([Line(num=0, parts=[".. coq::"])]),
500+ indent=0,
501+ ),
502+ CodeBlock(
503+ lines=deque(
504+ [
505+ Line(num=0, parts=[""]),
506+ Line(num=1, parts=[""]),
507+ Line(num=2, parts=[" Goal True."]),
508+ Line(num=3, parts=[" exact I. Qed."]),
509+ ]
510+ ),
511+ indent=0,
512+ ),
513+ ]
514+ <BLANKLINE>
515+ """
428516 beg , linum = 0 , 0
429517 for m in COQ_BLOCK .finditer (s ):
430518 indent = len (m .group ("indent" ))
431519 rst = StringView (s , beg , m .start ())
432- block = StringView (s , * m .span ())
520+ directive = StringView (s , * m .span ('directive' ))
521+ body = StringView (s , * m .span ('body' ))
433522
434523 linum , rst_lines = number_lines (rst , linum )
435- linum , block_lines = number_lines (block , linum )
436- directive = block_lines .popleft ()
524+ linum , directive_lines = number_lines (directive , linum )
525+ linum , body_lines = number_lines (body , linum )
526+
527+ # body_lines.popleft() # Discard initial blank
437528
438- yield Lit (rst_lines , directive = directive , indent = indent )
439- yield CodeBlock (block_lines , indent = indent )
529+ yield Lit (rst_lines , directive_lines = directive_lines , indent = indent )
530+ yield CodeBlock (body_lines , indent = indent )
440531 beg = m .end ()
441532 if beg < len (s ):
442533 rst = StringView (s , beg , len (s ))
443534 linum , lines = number_lines (rst , linum )
444- yield Lit (lines , directive = None , indent = None )
535+ yield Lit (lines , directive_lines = None , indent = None )
445536
446537# Conversion
447538# ----------
@@ -451,29 +542,34 @@ def measure_indentation(line):
451542 m = line .match (INDENTATION_RE )
452543 return m .end () - m .start ()
453544
545+ def redundant_directive (directive_lines , directive_indent , last_indent ):
546+ return (
547+ directive_lines and
548+ len (directive_lines ) == 1 and
549+ str (directive_lines [0 ]).strip () == DEFAULT_HEADER
550+ and directive_indent == last_indent
551+ )
552+
454553def trim_rst_block (block , last_indent , keep_empty ):
455554 strip_deque (block .lines )
456- directive_indent = block .indent # Stored here for convenience
457555 last_indent = measure_indentation (block .lines [- 1 ]) if block .lines else last_indent
458556
459- directive = block .directive
460- keep_empty = keep_empty and directive is not None
461- if (directive
462- and str (directive ).strip () == DEFAULT_HEADER
463- and directive_indent == last_indent ):
464- directive = None
557+ directive_lines = block .directive_lines
558+ keep_empty = keep_empty and directive_lines
559+ if redundant_directive (directive_lines , block .indent , last_indent ):
560+ directive_lines = None
465561
466- if not block .lines and not directive :
562+ if not block .lines and not directive_lines :
467563 if keep_empty :
468564 yield "(*||*)"
469565 yield ""
470566 else :
471567 yield "(*|"
472568 yield from (replace (l , QUOTE_PAIRS ) for l in block .lines )
473- if directive :
569+ if directive_lines :
474570 if block .lines :
475571 yield ""
476- yield directive
572+ yield from directive_lines
477573 yield "|*)"
478574 yield ""
479575
@@ -497,6 +593,40 @@ def rst2coq_lines(rst):
497593 return gen_coq (rst_partition (rst ))
498594
499595def rst2coq (rst ):
596+ """Translate a fragment of reST code to Coq.
597+
598+ >>> print(rst2coq('''
599+ ... Example:
600+ ...
601+ ... .. coq::
602+ ...
603+ ... Goal True.
604+ ...
605+ ... Second example:
606+ ...
607+ ... .. coq::
608+ ... :name:
609+ ... snd
610+ ...
611+ ... exact I. Qed.
612+ ... '''))
613+ (*|
614+ Example:
615+ |*)
616+ <BLANKLINE>
617+ Goal True.
618+ <BLANKLINE>
619+ (*|
620+ Second example:
621+ <BLANKLINE>
622+ .. coq::
623+ :name:
624+ snd
625+ |*)
626+ <BLANKLINE>
627+ exact I. Qed.
628+ <BLANKLINE>
629+ """
500630 return join_lines (rst2coq_lines (rst ))
501631
502632def rst2coq_marked (rst , point , marker ):
@@ -545,10 +675,5 @@ def main():
545675 contents = fstream .read ()
546676 sys .stdout .write (args .fn (contents ))
547677
548- def run_doctest ():
549- import sys
550- import doctest
551- doctest .debug (sys .modules .get ('__main__' ), "__main__.partition" , pm = True )
552-
553678if __name__ == '__main__' :
554679 main ()
0 commit comments