works on snap now

This commit is contained in:
Brando Miranda 2024-04-26 19:15:14 -07:00
commit c34f4ba3f2
3 changed files with 89 additions and 21 deletions

View File

@ -8,8 +8,9 @@ Execute
```bash ```bash
poetry build poetry build
``` ```
If the build is successful, the following command should succeed and output the current version of Pantograph. To run server tests:
``` bash ``` bash
python -m pantograph.server python -m pantograph.server
``` ```
The tests in `pantograph/server.py` also serve as simple interaction examples

View File

@ -2,7 +2,7 @@
Data structures for expressions and goals Data structures for expressions and goals
""" """
from dataclasses import dataclass from dataclasses import dataclass
from typing import Optional, Union from typing import Optional, Union, List
Expr = str Expr = str
@ -16,7 +16,7 @@ class Variable:
name: Optional[str] = None name: Optional[str] = None
@staticmethod @staticmethod
def _parse(payload: dict) -> "Variable": # Replace 'Self' with 'Variable' def parse(payload: dict) -> "Variable": # Replace 'Self' with 'Variable'
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")
@ -33,7 +33,7 @@ class Variable:
@dataclass(frozen=True) @dataclass(frozen=True)
class Goal: class Goal:
variables: list[Variable] variables: List[Variable]
target: Expr target: Expr
name: Optional[str] = None name: Optional[str] = None
is_conversion: bool = False is_conversion: bool = False
@ -43,11 +43,11 @@ class Goal:
return Goal(variables=[], target=target) return Goal(variables=[], target=target)
@staticmethod @staticmethod
def _parse(payload: dict) -> "Goal": # Replace 'Self' with 'Goal' def parse(payload: dict) -> "Goal": # Replace 'Self' with 'Goal'
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.get("isConversion", False) # Handle missing keys
return Goal(variables, target, name, is_conversion) return Goal(variables, target, name, is_conversion)
def __str__(self): def __str__(self):
@ -57,18 +57,27 @@ class Goal:
@dataclass(frozen=True) @dataclass(frozen=True)
class GoalState: class GoalState:
state_id: int state_id: int
goals: list[Goal] goals: List[Goal]
@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) -> "GoalState": # Replace 'Self' with 'GoalState'
payload: str state_id = payload.get("nextStateId", 0) # Handle missing keys
goals = [Goal.parse(g) for g in payload.get("goals", [])]
return GoalState(state_id, goals)
@dataclass(frozen=True) @dataclass(frozen=True)
class TacticHave: class TacticHave:
branch: str branch: str
Tactic = Union[TacticNormal, TacticHave] @dataclass(frozen=True)
class TacticCalc:
step: str
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()