feat: Have, calc, conv

This commit is contained in:
Leni Aniva 2024-04-22 22:38:20 -07:00
parent 6091a5191c
commit b4cfe7480f
Signed by: aniva
GPG Key ID: 4D9B1C8D10EA4C50
2 changed files with 83 additions and 16 deletions

View File

@ -16,7 +16,7 @@ class Variable:
name: Optional[str] = None name: Optional[str] = None
@staticmethod @staticmethod
def _parse(payload: dict) -> Self: def parse(payload: dict) -> Self:
name = payload.get("userName") name = payload.get("userName")
t = parse_expr(payload["type"]) t = parse_expr(payload["type"])
v = payload.get("value") v = payload.get("value")
@ -43,9 +43,9 @@ class Goal:
return Goal(variables=[], target=target) return Goal(variables=[], target=target)
@staticmethod @staticmethod
def _parse(payload: dict) -> Self: def parse(payload: dict) -> Self:
name = payload.get("userName") name = payload.get("userName")
variables = [Variable._parse(v) for v in payload["vars"]] variables = [Variable.parse(v) for v in payload["vars"]]
target = parse_expr(payload["target"]) target = parse_expr(payload["target"])
is_conversion = payload["isConversion"] is_conversion = payload["isConversion"]
return Goal(variables, target, name, is_conversion) return Goal(variables, target, name, is_conversion)
@ -62,13 +62,22 @@ class GoalState:
@property @property
def is_solved(self) -> bool: def is_solved(self) -> bool:
"""
WARNING: Does not handle dormant goals.
"""
return not self.goals return not self.goals
@dataclass(frozen=True) @staticmethod
class TacticNormal: def parse(payload: dict) -> Self:
payload: str state_id = payload["nextStateId"]
goals = [Goal.parse(g) for g in payload["goals"]]
return GoalState(state_id, goals)
@dataclass(frozen=True) @dataclass(frozen=True)
class TacticHave: class TacticHave:
branch: str branch: str
@dataclass(frozen=True)
class TacticCalc:
step: str
Tactic = Union[TacticNormal, TacticHave] Tactic = Union[str, TacticHave, TacticCalc]

View File

@ -3,7 +3,8 @@ Class which manages a Pantograph instance. All calls to the kernel uses this
interface. interface.
""" """
import json, pexpect, pathlib, unittest import json, pexpect, pathlib, unittest
from pantograph.expr import Variable, Goal, GoalState, Tactic, TacticNormal from pantograph.expr import Variable, Goal, GoalState, \
Tactic, TacticHave, TacticCalc
def _get_proc_cwd(): def _get_proc_cwd():
return pathlib.Path(__file__).parent return pathlib.Path(__file__).parent
@ -66,11 +67,15 @@ class Server:
return GoalState(state_id = result["stateId"], goals = [Goal.sentence(expr)]) return GoalState(state_id = result["stateId"], goals = [Goal.sentence(expr)])
def goal_tactic(self, state: GoalState, goal_id: int, tactic: Tactic) -> GoalState: def goal_tactic(self, state: GoalState, goal_id: int, tactic: Tactic) -> GoalState:
args = { "stateId": state.state_id, "goalId": goal_id} args = {"stateId": state.state_id, "goalId": goal_id}
if isinstance(tactic, TacticNormal): if isinstance(tactic, str):
args["tactic"] = tactic.payload args["tactic"] = tactic
elif isinstance(tactic, TacticHave):
args["have"] = tactic.branch
elif isinstance(tactic, TacticCalc):
args["calc"] = tactic.step
else: else:
raise Exception(f"Invalid tactic type: {tactic}") raise RuntimeError(f"Invalid tactic type: {tactic}")
result = self.run('goal.tactic', args) result = self.run('goal.tactic', args)
if "error" in result: if "error" in result:
raise ServerError(result["desc"]) raise ServerError(result["desc"])
@ -78,9 +83,28 @@ class Server:
raise ServerError(result["tacticErrors"]) raise ServerError(result["tacticErrors"])
if "parseError" in result: if "parseError" in result:
raise ServerError(result["parseError"]) raise ServerError(result["parseError"])
state_id = result["nextStateId"] return GoalState.parse(result)
goals = [Goal._parse(payload) for payload in result["goals"]]
return GoalState(state_id, goals) def goal_conv_begin(self, state: GoalState, goal_id: int) -> GoalState:
result = self.run('goal.tactic', {"stateId": state.state_id, "goalId": goal_id, "conv": True})
if "error" in result:
raise ServerError(result["desc"])
if "tacticErrors" in result:
raise ServerError(result["tacticErrors"])
if "parseError" in result:
raise ServerError(result["parseError"])
return GoalState.parse(result)
def goal_conv_end(self, state: GoalState) -> GoalState:
result = self.run('goal.tactic', {"stateId": state.state_id, "goalId": 0, "conv": False})
if "error" in result:
raise ServerError(result["desc"])
if "tacticErrors" in result:
raise ServerError(result["tacticErrors"])
if "parseError" in result:
raise ServerError(result["parseError"])
return GoalState.parse(result)
def get_version(): def get_version():
import subprocess import subprocess
@ -99,7 +123,7 @@ class TestServer(unittest.TestCase):
server = Server() server = Server()
state0 = server.goal_start("forall (p q: Prop), Or p q -> Or q p") state0 = server.goal_start("forall (p q: Prop), Or p q -> Or q p")
self.assertEqual(state0.state_id, 0) self.assertEqual(state0.state_id, 0)
state1 = server.goal_tactic(state0, goal_id=0, tactic=TacticNormal("intro a")) state1 = server.goal_tactic(state0, goal_id=0, tactic="intro a")
self.assertEqual(state1.state_id, 1) self.assertEqual(state1.state_id, 1)
self.assertEqual(state1.goals, [Goal( self.assertEqual(state1.goals, [Goal(
variables=[Variable(name="a", t="Prop")], variables=[Variable(name="a", t="Prop")],
@ -108,5 +132,39 @@ class TestServer(unittest.TestCase):
)]) )])
self.assertEqual(str(state1.goals[0]),"a : Prop\n⊢ ∀ (q : Prop), a q → q a") self.assertEqual(str(state1.goals[0]),"a : Prop\n⊢ ∀ (q : Prop), a q → q a")
def test_conv_calc(self):
server = Server()
state0 = server.goal_start("∀ (a b: Nat), (b = 2) -> 1 + a + 1 = a + b")
variables = [
Variable(name="a", t="Nat"),
Variable(name="b", t="Nat"),
Variable(name="h", t="b = 2"),
]
state1 = server.goal_tactic(state0, goal_id=0, tactic="intro a b h")
state2 = server.goal_tactic(state1, goal_id=0, tactic=TacticCalc("1 + a + 1 = a + 1 + 1"))
self.assertEqual(state2.goals, [
Goal(
variables,
target="1 + a + 1 = a + 1 + 1",
name='calc',
),
Goal(
variables,
target="a + 1 + 1 = a + b",
),
])
state_c1 = server.goal_conv_begin(state2, goal_id=0)
state_c2 = server.goal_tactic(state_c1, goal_id=0, tactic="rhs")
state_c3 = server.goal_tactic(state_c2, goal_id=0, tactic="rw [Nat.add_comm]")
state_c4 = server.goal_conv_end(state_c3)
state_c5 = server.goal_tactic(state_c4, goal_id=0, tactic="rfl")
self.assertTrue(state_c5.is_solved)
state3 = server.goal_tactic(state2, goal_id=1, tactic=TacticCalc("_ = a + 2"))
state4 = server.goal_tactic(state3, goal_id=0, tactic="rw [Nat.add_assoc]")
self.assertTrue(state4.is_solved)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()