2024-04-22 13:00:06 -07:00
|
|
|
|
"""
|
|
|
|
|
Class which manages a Pantograph instance. All calls to the kernel uses this
|
|
|
|
|
interface.
|
|
|
|
|
"""
|
|
|
|
|
import json, pexpect, pathlib, unittest
|
2024-04-22 13:11:28 -07:00
|
|
|
|
from pantograph.expr import Variable, Goal, GoalState, Tactic, TacticNormal
|
2024-04-19 16:47:35 -07:00
|
|
|
|
|
2024-04-22 13:00:06 -07:00
|
|
|
|
def _get_proc_cwd():
|
|
|
|
|
return pathlib.Path(__file__).parent
|
2024-04-19 16:47:35 -07:00
|
|
|
|
def _get_proc_path():
|
2024-04-22 13:00:06 -07:00
|
|
|
|
return _get_proc_cwd() / "pantograph"
|
|
|
|
|
|
|
|
|
|
class ServerError(Exception):
|
|
|
|
|
pass
|
2024-04-19 16:47:35 -07:00
|
|
|
|
|
|
|
|
|
class Server:
|
|
|
|
|
|
|
|
|
|
def __init__(self,
|
|
|
|
|
imports=["Init"],
|
|
|
|
|
options=[],
|
|
|
|
|
timeout=20,
|
|
|
|
|
maxread=1000000):
|
|
|
|
|
"""
|
|
|
|
|
timeout: Amount of time to wait for execution
|
|
|
|
|
maxread: Maximum number of characters to read (especially important for large proofs and catalogs)
|
|
|
|
|
"""
|
|
|
|
|
self.timeout = timeout
|
|
|
|
|
self.imports = imports
|
|
|
|
|
self.maxread = maxread
|
2024-04-22 13:00:06 -07:00
|
|
|
|
self.proc_cwd = _get_proc_cwd()
|
2024-04-19 16:47:35 -07:00
|
|
|
|
self.proc_path = _get_proc_path()
|
|
|
|
|
|
|
|
|
|
self.options = options
|
|
|
|
|
self.args = " ".join(imports + [f'--{opt}' for opt in options])
|
|
|
|
|
self.proc = None
|
|
|
|
|
self.restart()
|
|
|
|
|
|
|
|
|
|
def restart(self):
|
|
|
|
|
if self.proc is not None:
|
|
|
|
|
self.proc.close()
|
|
|
|
|
self.proc = pexpect.spawn(
|
|
|
|
|
f"{self.proc_path} {self.args}",
|
|
|
|
|
encoding="utf-8",
|
2024-04-22 13:00:06 -07:00
|
|
|
|
maxread=self.maxread,
|
|
|
|
|
cwd=self.proc_cwd,
|
2024-04-19 16:47:35 -07:00
|
|
|
|
)
|
|
|
|
|
self.proc.setecho(False)
|
|
|
|
|
|
|
|
|
|
def run(self, cmd, payload):
|
|
|
|
|
s = json.dumps(payload)
|
|
|
|
|
self.proc.sendline(f"{cmd} {s}")
|
|
|
|
|
try:
|
|
|
|
|
self.proc.expect("{.*}\r\n", timeout=self.timeout)
|
|
|
|
|
output = self.proc.match.group()
|
|
|
|
|
return json.loads(output)
|
|
|
|
|
except pexpect.exceptions.TIMEOUT:
|
|
|
|
|
raise pexpect.exceptions.TIMEOUT
|
|
|
|
|
|
|
|
|
|
def reset(self):
|
|
|
|
|
return self.run("reset", {})
|
|
|
|
|
|
2024-04-22 13:00:06 -07:00
|
|
|
|
def goal_start(self, expr: str) -> GoalState:
|
|
|
|
|
result = self.run('goal.start', {"expr": str(expr)})
|
|
|
|
|
if "error" in result:
|
|
|
|
|
raise ServerError(result["desc"])
|
|
|
|
|
return GoalState(state_id = result["stateId"], goals = [Goal.sentence(expr)])
|
2024-04-19 16:47:35 -07:00
|
|
|
|
|
2024-04-22 13:11:28 -07:00
|
|
|
|
def goal_tactic(self, state: GoalState, goalId: int, tactic: Tactic) -> GoalState:
|
|
|
|
|
args = { "stateId": state.state_id, "goalId": goalId }
|
|
|
|
|
if isinstance(tactic, TacticNormal):
|
|
|
|
|
args["tactic"] = tactic.payload
|
|
|
|
|
else:
|
|
|
|
|
raise Exception(f"Invalid tactic type: {tactic}")
|
|
|
|
|
result = self.run('goal.tactic', args)
|
2024-04-22 13:00:06 -07:00
|
|
|
|
if "error" in result:
|
|
|
|
|
raise ServerError(result["desc"])
|
|
|
|
|
if "tacticErrors" in result:
|
|
|
|
|
raise ServerError(result["tacticErrors"])
|
|
|
|
|
if "parseError" in result:
|
|
|
|
|
raise ServerError(result["parseError"])
|
|
|
|
|
state_id = result["nextStateId"]
|
|
|
|
|
goals = [Goal._parse(payload) for payload in result["goals"]]
|
|
|
|
|
return GoalState(state_id, goals)
|
2024-04-19 16:47:35 -07:00
|
|
|
|
|
2024-04-22 13:00:06 -07:00
|
|
|
|
def get_version():
|
2024-04-19 16:47:35 -07:00
|
|
|
|
import subprocess
|
2024-04-22 13:00:06 -07:00
|
|
|
|
with subprocess.Popen([_get_proc_path(), "--version"],
|
|
|
|
|
stdout=subprocess.PIPE,
|
|
|
|
|
cwd=_get_proc_cwd()) as p:
|
|
|
|
|
return p.communicate()[0].decode('utf-8').strip()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestServer(unittest.TestCase):
|
|
|
|
|
|
|
|
|
|
def test_version(self):
|
|
|
|
|
self.assertEqual(get_version(), "0.2.14")
|
2024-04-19 16:47:35 -07:00
|
|
|
|
|
2024-04-22 13:00:06 -07:00
|
|
|
|
def test_goal_start(self):
|
|
|
|
|
server = Server()
|
|
|
|
|
state0 = server.goal_start("forall (p q: Prop), Or p q -> Or q p")
|
|
|
|
|
self.assertEqual(state0.state_id, 0)
|
2024-04-22 13:11:28 -07:00
|
|
|
|
state1 = server.goal_tactic(state0, goalId=0, tactic=TacticNormal("intro a"))
|
2024-04-22 13:00:06 -07:00
|
|
|
|
self.assertEqual(state1.state_id, 1)
|
|
|
|
|
self.assertEqual(state1.goals, [Goal(
|
|
|
|
|
variables=[Variable(name="a", t="Prop")],
|
|
|
|
|
target="∀ (q : Prop), a ∨ q → q ∨ a",
|
|
|
|
|
name=None,
|
|
|
|
|
)])
|
|
|
|
|
self.assertEqual(str(state1.goals[0]),"a : Prop\n⊢ ∀ (q : Prop), a ∨ q → q ∨ a")
|
2024-04-19 16:47:35 -07:00
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
2024-04-22 13:00:06 -07:00
|
|
|
|
unittest.main()
|