Pantograph/pantograph/expr.py

148 lines
3.6 KiB
Python

"""
Data structuers for expressions and goals
"""
from dataclasses import dataclass, field
from typing import Optional, TypeAlias
Expr: TypeAlias = str
def parse_expr(payload: dict) -> Expr:
"""
:meta private:
"""
return payload["pp"]
@dataclass(frozen=True)
class Variable:
t: Expr
v: Optional[Expr] = None
name: Optional[str] = None
@staticmethod
def parse(payload: dict):
name = payload.get("userName")
t = parse_expr(payload["type"])
v = payload.get("value")
if v:
v = parse_expr(v)
return Variable(t, v, name)
def __str__(self):
"""
:meta public:
"""
result = self.name if self.name else "_"
result += f" : {self.t}"
if self.v:
result += f" := {self.v}"
return result
@dataclass(frozen=True)
class Goal:
variables: list[Variable]
target: Expr
sibling_dep: list[int] = field(default_factory=lambda: [])
name: Optional[str] = None
is_conversion: bool = False
@staticmethod
def sentence(target: Expr):
"""
:meta public:
"""
return Goal(variables=[], target=target)
@staticmethod
def parse(payload: dict, sibling_map: dict[str, int]):
name = payload.get("userName")
variables = [Variable.parse(v) for v in payload["vars"]]
target = parse_expr(payload["target"])
is_conversion = payload["isConversion"]
dependents = payload["target"]["dependentMVars"]
sibling_dep = [sibling_map[d] for d in dependents if d in sibling_map]
return Goal(variables, target, sibling_dep, name, is_conversion)
def __str__(self):
"""
:meta public:
"""
front = "|" if self.is_conversion else ""
return "\n".join(str(v) for v in self.variables) + \
f"\n{front} {self.target}"
@dataclass(frozen=True)
class GoalState:
state_id: int
goals: list[Goal]
_sentinel: list[int]
def __del__(self):
self._sentinel.append(self.state_id)
@property
def is_solved(self) -> bool:
"""
WARNING: Does not handle dormant goals.
:meta public:
"""
return not self.goals
@staticmethod
def parse_inner(state_id: int, goals: list, _sentinel: list[int]):
assert _sentinel is not None
goal_names = { g["name"]: i for i, g in enumerate(goals) }
goals = [Goal.parse(g, goal_names) for g in goals]
return GoalState(state_id, goals, _sentinel)
@staticmethod
def parse(payload: dict, _sentinel: list[int]):
return GoalState.parse_inner(payload["nextStateId"], payload["goals"], _sentinel)
def __str__(self):
"""
:meta public:
"""
return "\n".join([str(g) for g in self.goals])
@dataclass(frozen=True)
class TacticHave:
"""
The `have` tactic, equivalent to
```lean
have {binder_name} : {branch} := ...
```
"""
branch: str
binder_name: Optional[str] = None
@dataclass(frozen=True)
class TacticLet:
"""
The `let` tactic, equivalent to
```lean
let {binder_name} : {branch} := ...
```
"""
branch: str
binder_name: Optional[str] = None
@dataclass(frozen=True)
class TacticCalc:
"""
The `calc` tactic, equivalent to
```lean
calc {step} := ...
```
You can use `_` in the step.
"""
step: str
@dataclass(frozen=True)
class TacticExpr:
"""
Assigns an expression to the current goal
"""
expr: str
Tactic: TypeAlias = str | TacticHave | TacticLet | TacticCalc | TacticExpr