summaryrefslogtreecommitdiff
path: root/doc/tools/coqrst/repl
diff options
context:
space:
mode:
Diffstat (limited to 'doc/tools/coqrst/repl')
-rw-r--r--doc/tools/coqrst/repl/__init__.py0
-rw-r--r--doc/tools/coqrst/repl/ansicolors.py99
-rw-r--r--doc/tools/coqrst/repl/coqtop.py103
3 files changed, 202 insertions, 0 deletions
diff --git a/doc/tools/coqrst/repl/__init__.py b/doc/tools/coqrst/repl/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/doc/tools/coqrst/repl/__init__.py
diff --git a/doc/tools/coqrst/repl/ansicolors.py b/doc/tools/coqrst/repl/ansicolors.py
new file mode 100644
index 00000000..495eea91
--- /dev/null
+++ b/doc/tools/coqrst/repl/ansicolors.py
@@ -0,0 +1,99 @@
+##########################################################################
+## # The Coq Proof Assistant / The Coq Development Team ##
+## v # INRIA, CNRS and contributors - Copyright 1999-2018 ##
+## <O___,, # (see CREDITS file for the list of authors) ##
+## \VV/ ###############################################################
+## // # This file is distributed under the terms of the ##
+## # GNU Lesser General Public License Version 2.1 ##
+## # (see LICENSE file for the text of the license) ##
+##########################################################################
+"""
+Parse Coq's ANSI output.
+========================
+
+Translated to Python from Coq's terminal.ml.
+"""
+
+# pylint: disable=too-many-return-statements, too-many-branches
+
+def parse_color(style, offset):
+ color = style[offset] % 10
+ if color == 0:
+ return ("black", 1)
+ elif color == 1:
+ return ("red", 1)
+ elif color == 2:
+ return ("green", 1)
+ elif color == 3:
+ return ("yellow", 1)
+ elif color == 4:
+ return ("blue", 1)
+ elif color == 5:
+ return ("magenta", 1)
+ elif color == 6:
+ return ("cyan", 1)
+ elif color == 7:
+ return ("white", 1)
+ elif color == 9:
+ return ("default", 1)
+ elif color == 8:
+ nxt = style[offset + 1]
+ if nxt == 5:
+ return ("index-{}".format(style[offset + 1]), 2)
+ elif nxt == 2:
+ return ("rgb-{}-{}-{}".format(*style[offset+1:offset+4]), 4)
+ else:
+ raise ValueError("{}, {}".format(style, offset))
+ else:
+ raise ValueError()
+
+def parse_style(style, offset, acc):
+ offset = 0
+ while offset < len(style):
+ head = style[offset]
+
+ if head == 0:
+ acc.append("reset")
+ elif head == 1:
+ acc.append("bold")
+ elif head == 3:
+ acc.append("italic")
+ elif head == 4:
+ acc.append("underline")
+ elif head == 7:
+ acc.append("negative")
+ elif head == 22:
+ acc.append("no-bold")
+ elif head == 23:
+ acc.append("no-italic")
+ elif head == 24:
+ acc.append("no-underline")
+ elif head == 27:
+ acc.append("no-negative")
+ else:
+ color, suboffset = parse_color(style, offset)
+ offset += suboffset - 1
+ if 30 <= head < 40:
+ acc.append("fg-{}".format(color))
+ elif 40 <= head < 50:
+ acc.append("bg-{}".format(color))
+ elif 90 <= head < 100:
+ acc.append("fg-light-{}".format(color))
+ elif 100 <= head < 110:
+ acc.append("bg-light-{}".format(color))
+
+ offset += 1
+
+def parse_ansi(code):
+ """Parse an ansi code into a collection of CSS classes.
+
+ :param code: A sequence of ‘;’-separated ANSI codes. Do not include the
+ leading ‘^[[’ or the final ‘m’
+ """
+ classes = []
+ parse_style([int(c) for c in code.split(';')], 0, classes)
+ return ["ansi-" + cls for cls in classes]
+
+if __name__ == '__main__':
+ # As produced by Coq with ‘Check nat.’
+ print(parse_ansi("92;49;22;23;24;27"))
diff --git a/doc/tools/coqrst/repl/coqtop.py b/doc/tools/coqrst/repl/coqtop.py
new file mode 100644
index 00000000..aeadce4c
--- /dev/null
+++ b/doc/tools/coqrst/repl/coqtop.py
@@ -0,0 +1,103 @@
+##########################################################################
+## # The Coq Proof Assistant / The Coq Development Team ##
+## v # INRIA, CNRS and contributors - Copyright 1999-2018 ##
+## <O___,, # (see CREDITS file for the list of authors) ##
+## \VV/ ###############################################################
+## // # This file is distributed under the terms of the ##
+## # GNU Lesser General Public License Version 2.1 ##
+## # (see LICENSE file for the text of the license) ##
+##########################################################################
+"""
+Drive coqtop with Python!
+=========================
+
+This module is a simple pexpect-based driver for coqtop, based on the old
+REPL interface.
+"""
+
+import os
+import re
+
+import pexpect
+
+class CoqTop:
+ """Create an instance of coqtop.
+
+ Use this as a context manager: no instance of coqtop is created until
+ you call `__enter__`. coqtop is terminated when you `__exit__` the
+ context manager.
+
+ Sentence parsing is very basic for now (a "." in a quoted string will
+ confuse it).
+ """
+
+ COQTOP_PROMPT = re.compile("\r\n[^< ]+ < ")
+
+ def __init__(self, coqtop_bin=None, color=False, args=None) -> str:
+ """Configure a coqtop instance (but don't start it yet).
+
+ :param coqtop_bin: The path to coqtop; uses $COQBIN by default, falling back to "coqtop"
+ :param color: When True, tell coqtop to produce ANSI color codes (see
+ the ansicolors module)
+ :param args: Additional arugments to coqtop.
+ """
+ self.coqtop_bin = coqtop_bin or os.path.join(os.getenv('COQBIN', ""), "coqtop")
+ if not pexpect.utils.which(self.coqtop_bin):
+ raise ValueError("coqtop binary not found: '{}'".format(self.coqtop_bin))
+ self.args = (args or []) + ["-boot", "-color", "on"] * color
+ self.coqtop = None
+
+ def __enter__(self):
+ if self.coqtop:
+ raise ValueError("This module isn't re-entrant")
+ self.coqtop = pexpect.spawn(self.coqtop_bin, args=self.args, echo=False, encoding="utf-8")
+ # Disable delays (http://pexpect.readthedocs.io/en/stable/commonissues.html?highlight=delaybeforesend)
+ self.coqtop.delaybeforesend = 0
+ self.next_prompt()
+ return self
+
+ def __exit__(self, type, value, traceback):
+ self.coqtop.kill(9)
+
+ def next_prompt(self):
+ "Wait for the next coqtop prompt, and return the output preceeding it."
+ self.coqtop.expect(CoqTop.COQTOP_PROMPT, timeout = 10)
+ return self.coqtop.before
+
+ def sendone(self, sentence):
+ """Send a single sentence to coqtop.
+
+ :sentence: One Coq sentence (otherwise, Coqtop will produce multiple
+ prompts and we'll get confused)
+ """
+ # Suppress newlines, but not spaces: they are significant in notations
+ sentence = re.sub(r"[\r\n]+", " ", sentence).strip()
+ self.coqtop.sendline(sentence)
+ try:
+ output = self.next_prompt()
+ except:
+ print("Error while sending the following sentence to coqtop: {}".format(sentence))
+ raise
+ # print("Got {}".format(repr(output)))
+ return output
+
+def sendmany(*sentences):
+ """A small demo: send each sentence in sentences and print the output"""
+ with CoqTop() as coqtop:
+ for sentence in sentences:
+ print("=====================================")
+ print(sentence)
+ print("-------------------------------------")
+ response = coqtop.sendone(sentence)
+ print(response)
+
+def main():
+ """Run a simple performance test and demo `sendmany`"""
+ with CoqTop() as coqtop:
+ for _ in range(200):
+ print(repr(coqtop.sendone("Check nat.")))
+ sendmany("Goal False -> True.", "Proof.", "intros H.",
+ "Check H.", "Chchc.", "apply I.", "Qed.")
+
+if __name__ == '__main__':
+ main()