unicode-logic-kit 0.31.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- unicode_logic_kit/__init__.py +385 -0
- unicode_logic_kit/__main__.py +520 -0
- unicode_logic_kit/_deadline.py +219 -0
- unicode_logic_kit/ace/__init__.py +126 -0
- unicode_logic_kit/ace/_align.py +135 -0
- unicode_logic_kit/ace/chem_lexicon.py +128 -0
- unicode_logic_kit/ace/drs_reader.py +570 -0
- unicode_logic_kit/ace/mapping.py +666 -0
- unicode_logic_kit/ace/reverse_modal.py +138 -0
- unicode_logic_kit/ace/runner.py +551 -0
- unicode_logic_kit/ace/translate.py +452 -0
- unicode_logic_kit/ace/verbalize.py +1070 -0
- unicode_logic_kit/api.py +1284 -0
- unicode_logic_kit/atp/__init__.py +177 -0
- unicode_logic_kit/atp/_ascii_names.py +113 -0
- unicode_logic_kit/atp/_html.py +72 -0
- unicode_logic_kit/atp/_substructural_input.py +228 -0
- unicode_logic_kit/atp/_tff_problem.py +715 -0
- unicode_logic_kit/atp/_tptp_problem.py +1111 -0
- unicode_logic_kit/atp/_writer_support.py +289 -0
- unicode_logic_kit/atp/clingo_backend.py +1180 -0
- unicode_logic_kit/atp/cvc5_backend.py +1385 -0
- unicode_logic_kit/atp/eprover_backend.py +732 -0
- unicode_logic_kit/atp/finite_domain.py +1055 -0
- unicode_logic_kit/atp/fitch.py +1547 -0
- unicode_logic_kit/atp/fitch_search.py +551 -0
- unicode_logic_kit/atp/hets_backend.py +339 -0
- unicode_logic_kit/atp/hybrid_down.py +120 -0
- unicode_logic_kit/atp/incremental.py +250 -0
- unicode_logic_kit/atp/kripke_enum.py +741 -0
- unicode_logic_kit/atp/lambek.py +436 -0
- unicode_logic_kit/atp/leo3_backend.py +332 -0
- unicode_logic_kit/atp/linear.py +738 -0
- unicode_logic_kit/atp/lj.py +705 -0
- unicode_logic_kit/atp/logic_backends.py +566 -0
- unicode_logic_kit/atp/ltl_tableau.py +1084 -0
- unicode_logic_kit/atp/minizinc_backend.py +1402 -0
- unicode_logic_kit/atp/modal_tableau.py +1382 -0
- unicode_logic_kit/atp/nanocop_backend.py +410 -0
- unicode_logic_kit/atp/portfolio.py +489 -0
- unicode_logic_kit/atp/protocol.py +1803 -0
- unicode_logic_kit/atp/prover9_entailment.py +1153 -0
- unicode_logic_kit/atp/resolution.py +1376 -0
- unicode_logic_kit/atp/resolution_check.py +1114 -0
- unicode_logic_kit/atp/sequent.py +1050 -0
- unicode_logic_kit/atp/tableau.py +921 -0
- unicode_logic_kit/atp/tableau_check.py +543 -0
- unicode_logic_kit/atp/tptp_ncl.py +811 -0
- unicode_logic_kit/atp/tptp_tff.py +1546 -0
- unicode_logic_kit/atp/tstp.py +1333 -0
- unicode_logic_kit/atp/tstp_check.py +1096 -0
- unicode_logic_kit/atp/twee_backend.py +236 -0
- unicode_logic_kit/atp/twee_check.py +711 -0
- unicode_logic_kit/atp/twee_entailment.py +953 -0
- unicode_logic_kit/atp/vampire_entailment.py +540 -0
- unicode_logic_kit/atp/z3_arith.py +470 -0
- unicode_logic_kit/atp/z3_equivalence.py +36 -0
- unicode_logic_kit/atp/z3_fuzzy.py +362 -0
- unicode_logic_kit/atp/z3_input.py +500 -0
- unicode_logic_kit/atp/z3_models.py +208 -0
- unicode_logic_kit/chem/__init__.py +88 -0
- unicode_logic_kit/chem/_naming.py +284 -0
- unicode_logic_kit/chem/cache.py +185 -0
- unicode_logic_kit/chem/interop.py +244 -0
- unicode_logic_kit/chem/mol.py +525 -0
- unicode_logic_kit/chem/signature.py +112 -0
- unicode_logic_kit/comorphism.py +497 -0
- unicode_logic_kit/dl/__init__.py +384 -0
- unicode_logic_kit/dl/classification.py +227 -0
- unicode_logic_kit/dl/concepts.py +632 -0
- unicode_logic_kit/dl/datatypes.py +818 -0
- unicode_logic_kit/dl/owl_functional.py +2433 -0
- unicode_logic_kit/dl/owl_manchester.py +1637 -0
- unicode_logic_kit/dl/owl_reasoner.py +790 -0
- unicode_logic_kit/dl/parser.py +391 -0
- unicode_logic_kit/dl/tableau.py +4048 -0
- unicode_logic_kit/dl/translate.py +2704 -0
- unicode_logic_kit/drt/__init__.py +94 -0
- unicode_logic_kit/drt/export.py +179 -0
- unicode_logic_kit/drt/nodes.py +506 -0
- unicode_logic_kit/drt/parser.py +965 -0
- unicode_logic_kit/drt/resolve.py +195 -0
- unicode_logic_kit/drt/reverse.py +175 -0
- unicode_logic_kit/eval/__init__.py +106 -0
- unicode_logic_kit/eval/batch.py +382 -0
- unicode_logic_kit/eval/canonical.py +663 -0
- unicode_logic_kit/eval/chem_batch.py +606 -0
- unicode_logic_kit/eval/converses.py +200 -0
- unicode_logic_kit/eval/datasets/__init__.py +136 -0
- unicode_logic_kit/eval/datasets/_base.py +263 -0
- unicode_logic_kit/eval/datasets/_proofwriter_proof.py +422 -0
- unicode_logic_kit/eval/datasets/c3po.py +678 -0
- unicode_logic_kit/eval/datasets/folio.py +158 -0
- unicode_logic_kit/eval/datasets/fracas.py +418 -0
- unicode_logic_kit/eval/datasets/groves.py +191 -0
- unicode_logic_kit/eval/datasets/logicbench.py +467 -0
- unicode_logic_kit/eval/datasets/logicnli.py +303 -0
- unicode_logic_kit/eval/datasets/malls.py +133 -0
- unicode_logic_kit/eval/datasets/pfolio.py +594 -0
- unicode_logic_kit/eval/datasets/pmb.py +242 -0
- unicode_logic_kit/eval/datasets/prontoqa.py +611 -0
- unicode_logic_kit/eval/datasets/proofwriter.py +1431 -0
- unicode_logic_kit/eval/datasets/proverqa.py +674 -0
- unicode_logic_kit/eval/datasets/willow.py +478 -0
- unicode_logic_kit/eval/equivalence.py +466 -0
- unicode_logic_kit/eval/exercise_gen.py +533 -0
- unicode_logic_kit/eval/explain.py +791 -0
- unicode_logic_kit/eval/generality.py +750 -0
- unicode_logic_kit/eval/metric_hf.py +458 -0
- unicode_logic_kit/eval/predicate_match.py +343 -0
- unicode_logic_kit/eval/theory_check.py +1170 -0
- unicode_logic_kit/eval/validate.py +306 -0
- unicode_logic_kit/fol/__init__.py +177 -0
- unicode_logic_kit/fol/_atom_keys.py +510 -0
- unicode_logic_kit/fol/_fol_nodes.py +3586 -0
- unicode_logic_kit/fol/_free_parameters.py +105 -0
- unicode_logic_kit/fol/_ho_nodes.py +448 -0
- unicode_logic_kit/fol/_hybrid_nodes.py +308 -0
- unicode_logic_kit/fol/_identifiers.py +1091 -0
- unicode_logic_kit/fol/_lambek_nodes.py +112 -0
- unicode_logic_kit/fol/_linear_nodes.py +352 -0
- unicode_logic_kit/fol/_modal_nodes.py +1467 -0
- unicode_logic_kit/fol/_msfl_nodes.py +2196 -0
- unicode_logic_kit/fol/_numeral_symbols.py +231 -0
- unicode_logic_kit/fol/_so_nodes.py +200 -0
- unicode_logic_kit/fol/_symbol_names.py +81 -0
- unicode_logic_kit/fol/_team_nodes.py +181 -0
- unicode_logic_kit/fol/_tptp_symbols.py +551 -0
- unicode_logic_kit/fol/_truth_constants.py +117 -0
- unicode_logic_kit/fol/casl_export.py +1135 -0
- unicode_logic_kit/fol/casl_import.py +929 -0
- unicode_logic_kit/fol/derivation.py +367 -0
- unicode_logic_kit/fol/dialect_detect.py +70 -0
- unicode_logic_kit/fol/dialect_repair.py +537 -0
- unicode_logic_kit/fol/frames.py +637 -0
- unicode_logic_kit/fol/grammars/terminals.lark +31 -0
- unicode_logic_kit/fol/lambda_tools.py +297 -0
- unicode_logic_kit/fol/latex_input.py +429 -0
- unicode_logic_kit/fol/modal_translation.py +944 -0
- unicode_logic_kit/fol/msflparser.py +1033 -0
- unicode_logic_kit/fol/naming.py +422 -0
- unicode_logic_kit/fol/nodes.py +241 -0
- unicode_logic_kit/fol/normalforms.py +492 -0
- unicode_logic_kit/fol/pal.py +287 -0
- unicode_logic_kit/fol/prolog_export.py +566 -0
- unicode_logic_kit/fol/prolog_input.py +505 -0
- unicode_logic_kit/fol/prover9_input.py +1325 -0
- unicode_logic_kit/fol/qml.py +1760 -0
- unicode_logic_kit/fol/qmltp_input.py +525 -0
- unicode_logic_kit/fol/sanitize.py +221 -0
- unicode_logic_kit/fol/serialize.py +79 -0
- unicode_logic_kit/fol/signature.py +1290 -0
- unicode_logic_kit/fol/simplify_check.py +544 -0
- unicode_logic_kit/fol/spans.py +594 -0
- unicode_logic_kit/fol/tptp_input.py +1503 -0
- unicode_logic_kit/fol/tptp_repair.py +941 -0
- unicode_logic_kit/fol/unification.py +157 -0
- unicode_logic_kit/fol/verbalize.py +263 -0
- unicode_logic_kit/hets/__init__.py +163 -0
- unicode_logic_kit/hets/bridge.py +142 -0
- unicode_logic_kit/hets/client.py +748 -0
- unicode_logic_kit/hets/docker.py +420 -0
- unicode_logic_kit/hets/dol.py +712 -0
- unicode_logic_kit/hets/haskell_json.py +355 -0
- unicode_logic_kit/hets/owl_backend.py +794 -0
- unicode_logic_kit/hets/owl_cli.py +598 -0
- unicode_logic_kit/hets/symbols.py +512 -0
- unicode_logic_kit/hol/__init__.py +140 -0
- unicode_logic_kit/hol/_ho_common.py +323 -0
- unicode_logic_kit/hol/_isabelle_binders.py +125 -0
- unicode_logic_kit/hol/classical.py +812 -0
- unicode_logic_kit/hol/deepshallow/__init__.py +45 -0
- unicode_logic_kit/hol/deepshallow/_common.py +177 -0
- unicode_logic_kit/hol/deepshallow/conditional.py +225 -0
- unicode_logic_kit/hol/deepshallow/intuitionistic.py +181 -0
- unicode_logic_kit/hol/deepshallow/modal.py +217 -0
- unicode_logic_kit/hol/deepshallow/qml.py +406 -0
- unicode_logic_kit/hol/deepshallow/relevant.py +206 -0
- unicode_logic_kit/hol/free.py +753 -0
- unicode_logic_kit/hol/goedel.py +336 -0
- unicode_logic_kit/hol/ho_modal.py +1743 -0
- unicode_logic_kit/hol/intuitionistic.py +403 -0
- unicode_logic_kit/hol/isabelle_conditional.py +593 -0
- unicode_logic_kit/hol/isabelle_modal.py +1908 -0
- unicode_logic_kit/hol/isabelle_relevant.py +412 -0
- unicode_logic_kit/hol/isabelle_runner.py +1147 -0
- unicode_logic_kit/hol/isabelle_substructural.py +884 -0
- unicode_logic_kit/hol/lean.py +1018 -0
- unicode_logic_kit/hol/manyvalued.py +921 -0
- unicode_logic_kit/hol/secondorder.py +687 -0
- unicode_logic_kit/hol/thf_modal.py +941 -0
- unicode_logic_kit/hol/thirdorder.py +397 -0
- unicode_logic_kit/ilp/__init__.py +89 -0
- unicode_logic_kit/ilp/readback.py +389 -0
- unicode_logic_kit/ilp/separation.py +153 -0
- unicode_logic_kit/ilp/task.py +730 -0
- unicode_logic_kit/logic.py +163 -0
- unicode_logic_kit/mcp/__init__.py +28 -0
- unicode_logic_kit/mcp/__main__.py +5 -0
- unicode_logic_kit/mcp/chem_tools.py +1031 -0
- unicode_logic_kit/mcp/server.py +2453 -0
- unicode_logic_kit/mcp/syntax_spec.py +681 -0
- unicode_logic_kit/prob/__init__.py +53 -0
- unicode_logic_kit/prob/_bdd.py +225 -0
- unicode_logic_kit/prob/_column_gen.py +668 -0
- unicode_logic_kit/prob/distribution.py +686 -0
- unicode_logic_kit/prob/nilsson.py +470 -0
- unicode_logic_kit/py.typed +0 -0
- unicode_logic_kit/semantics/__init__.py +137 -0
- unicode_logic_kit/semantics/_modal_reject.py +156 -0
- unicode_logic_kit/semantics/action_models.py +466 -0
- unicode_logic_kit/semantics/asp_models.py +1200 -0
- unicode_logic_kit/semantics/conditional.py +580 -0
- unicode_logic_kit/semantics/dynamic_epistemic.py +95 -0
- unicode_logic_kit/semantics/free_logic.py +913 -0
- unicode_logic_kit/semantics/fuzzy.py +384 -0
- unicode_logic_kit/semantics/fuzzy_kripke.py +442 -0
- unicode_logic_kit/semantics/intuitionistic.py +581 -0
- unicode_logic_kit/semantics/kripke.py +1139 -0
- unicode_logic_kit/semantics/manyvalued.py +580 -0
- unicode_logic_kit/semantics/matrix.py +342 -0
- unicode_logic_kit/semantics/model_eval.py +1135 -0
- unicode_logic_kit/semantics/modelfinder.py +1036 -0
- unicode_logic_kit/semantics/nonmonotonic.py +372 -0
- unicode_logic_kit/semantics/relevant.py +331 -0
- unicode_logic_kit/semantics/secondorder.py +657 -0
- unicode_logic_kit/semantics/structures.py +352 -0
- unicode_logic_kit/semantics/tarski.py +975 -0
- unicode_logic_kit/semantics/team.py +315 -0
- unicode_logic_kit/semantics/team_translation.py +416 -0
- unicode_logic_kit/semantics/thirdorder.py +358 -0
- unicode_logic_kit/semantics/tnorm.py +85 -0
- unicode_logic_kit/semantics/truthtable.py +201 -0
- unicode_logic_kit-0.31.0.dist-info/METADATA +333 -0
- unicode_logic_kit-0.31.0.dist-info/RECORD +237 -0
- unicode_logic_kit-0.31.0.dist-info/WHEEL +4 -0
- unicode_logic_kit-0.31.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
"""CCG-style derivation trees with lambda-semantics, and renderers.
|
|
2
|
+
|
|
3
|
+
A :class:`CCGDerivation` is a node in a bottom-up composition tree in the spirit of
|
|
4
|
+
ccg2lambda / depccg: a surface ``word`` (leaves), a CCG ``category`` (a display
|
|
5
|
+
string such as ``"(S\\NP)/NP"``), the ``term`` (a real toolkit lambda-term
|
|
6
|
+
:class:`~unicode_logic_kit.fol.nodes.Node`), a combinator ``rule`` label
|
|
7
|
+
(``fa`` / ``ba`` / ``bx`` / ``conj`` / ``lex`` / ``rp`` / …), and ``children``.
|
|
8
|
+
|
|
9
|
+
The semantics is genuinely *composed*, not hand-written: :meth:`CCGDerivation.forward`
|
|
10
|
+
and :meth:`CCGDerivation.backward` apply one child's term to the other and reduce it
|
|
11
|
+
with the toolkit's :func:`beta_reduce`, so the ``term`` at every internal node is the
|
|
12
|
+
actual beta-normal form of the composition — the same machinery that backs
|
|
13
|
+
``reduce_trace`` / ``beta_reduce``.
|
|
14
|
+
|
|
15
|
+
Three renderers are provided, mirroring the toolkit's existing ``to_unicode_str`` /
|
|
16
|
+
``to_latex`` / ``tree_str`` split:
|
|
17
|
+
|
|
18
|
+
- :meth:`~CCGDerivation.to_text` — a Unicode "prooftree": premises row, an inference
|
|
19
|
+
bar with the combinator at its right, then the category over the lambda-term. This
|
|
20
|
+
is a genuinely new rendering style for the toolkit (neither the indented
|
|
21
|
+
``render_sequent_proof`` nor the Fitch-bar ``render_fitch`` draws a Gentzen bar).
|
|
22
|
+
- :meth:`~CCGDerivation.to_latex` — a ``bussproofs`` ``prooftree`` (needs
|
|
23
|
+
``\\usepackage{bussproofs}``); each node's term is emitted via ``Node.to_latex``.
|
|
24
|
+
- :meth:`~CCGDerivation.to_html` — a self-contained, theme-aware HTML page in the
|
|
25
|
+
ccg2lambda idiom (category red, lambda-term blue, combinator at the bar).
|
|
26
|
+
|
|
27
|
+
:func:`reduction_derivation` turns a beta-reduction path (``reduce_trace``) into a
|
|
28
|
+
``CCGDerivation`` chain, so a lambda reduction can be drawn with the same renderers.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from dataclasses import dataclass
|
|
32
|
+
from typing import List, Optional, Tuple
|
|
33
|
+
|
|
34
|
+
from .nodes import Node, Application, beta_reduce, beta_eta_normalize
|
|
35
|
+
|
|
36
|
+
__all__ = ["CCGDerivation", "reduction_derivation"]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(frozen=True)
|
|
40
|
+
class CCGDerivation:
|
|
41
|
+
"""One (sub)derivation: its combinator ``rule``, CCG ``category``, lambda ``term``,
|
|
42
|
+
optional surface ``word`` (for leaves), and ``children`` (the premises).
|
|
43
|
+
|
|
44
|
+
Build leaves with :meth:`leaf` and combine them with :meth:`forward` (``fa``),
|
|
45
|
+
:meth:`backward` (``ba``), :meth:`unary` (``lex`` / ``rp`` / type-changing), or the
|
|
46
|
+
low-level :meth:`combine`. The composing builders reduce the applied term to
|
|
47
|
+
beta-normal form, so ``term`` is always the real semantics at that node.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
rule: Optional[str] = None
|
|
51
|
+
category: Optional[str] = None
|
|
52
|
+
term: Optional[Node] = None
|
|
53
|
+
word: Optional[str] = None
|
|
54
|
+
children: Tuple["CCGDerivation", ...] = ()
|
|
55
|
+
|
|
56
|
+
def __post_init__(self):
|
|
57
|
+
"""Coerce ``children`` to a tuple so this frozen node stays hashable."""
|
|
58
|
+
if not isinstance(self.children, tuple):
|
|
59
|
+
object.__setattr__(self, "children", tuple(self.children))
|
|
60
|
+
|
|
61
|
+
# ---- constructors ---------------------------------------------------- #
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def leaf(cls, word: str, category: str, term: Node) -> "CCGDerivation":
|
|
65
|
+
"""A lexical leaf: a ``word`` with its ``category`` and lambda ``term``."""
|
|
66
|
+
return cls(rule=None, category=category, term=term, word=word, children=())
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def unary(cls, rule: str, category: str, child: "CCGDerivation",
|
|
70
|
+
term: Optional[Node] = None) -> "CCGDerivation":
|
|
71
|
+
"""A one-premise step (``lex`` type-raising, ``rp`` punctuation, …).
|
|
72
|
+
|
|
73
|
+
``term`` defaults to the child's term (an identity step such as ``rp``); pass
|
|
74
|
+
an explicit ``term`` for a genuine type-change such as ``lex``.
|
|
75
|
+
"""
|
|
76
|
+
return cls(rule=rule, category=category,
|
|
77
|
+
term=child.term if term is None else term,
|
|
78
|
+
word=None, children=(child,))
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def combine(cls, rule: str, category: str,
|
|
82
|
+
children: List["CCGDerivation"], term: Node) -> "CCGDerivation":
|
|
83
|
+
"""A general step with an explicitly supplied composed ``term``."""
|
|
84
|
+
return cls(rule=rule, category=category, term=term,
|
|
85
|
+
word=None, children=tuple(children))
|
|
86
|
+
|
|
87
|
+
@classmethod
|
|
88
|
+
def forward(cls, category: str, functor: "CCGDerivation",
|
|
89
|
+
argument: "CCGDerivation", *, rule: str = "fa",
|
|
90
|
+
eta: bool = False) -> "CCGDerivation":
|
|
91
|
+
"""Forward application (``X/Y Y -> X``): apply the LEFT child's term to the
|
|
92
|
+
right and beta-normalise. The composed term is ``beta_reduce(functor.term
|
|
93
|
+
argument.term)``. Raises :class:`ReductionLimitError` if that reduction does
|
|
94
|
+
not terminate.
|
|
95
|
+
"""
|
|
96
|
+
term = _apply(functor.term, argument.term, eta)
|
|
97
|
+
return cls(rule=rule, category=category, term=term,
|
|
98
|
+
word=None, children=(functor, argument))
|
|
99
|
+
|
|
100
|
+
@classmethod
|
|
101
|
+
def backward(cls, category: str, argument: "CCGDerivation",
|
|
102
|
+
functor: "CCGDerivation", *, rule: str = "ba",
|
|
103
|
+
eta: bool = False) -> "CCGDerivation":
|
|
104
|
+
"""Backward application (``Y X\\Y -> X``): the functor is the RIGHT child; its
|
|
105
|
+
term is applied to the left child's term and beta-normalised. Children are kept
|
|
106
|
+
in surface order ``(argument, functor)``. Raises :class:`ReductionLimitError`
|
|
107
|
+
if that reduction does not terminate.
|
|
108
|
+
"""
|
|
109
|
+
term = _apply(functor.term, argument.term, eta)
|
|
110
|
+
return cls(rule=rule, category=category, term=term,
|
|
111
|
+
word=None, children=(argument, functor))
|
|
112
|
+
|
|
113
|
+
# ---- renderers ------------------------------------------------------- #
|
|
114
|
+
|
|
115
|
+
def to_text(self, gap: int = 3) -> str:
|
|
116
|
+
"""Render as a Unicode prooftree (premises above an inference bar above the
|
|
117
|
+
conclusion; the combinator label sits at the right end of the bar).
|
|
118
|
+
|
|
119
|
+
Column widths are computed with ``len`` (codepoint count), like the toolkit's
|
|
120
|
+
``tree_str`` / ``render_fitch``; double-width (CJK) or combining glyphs may
|
|
121
|
+
therefore not align perfectly in a monospace terminal — use ``to_latex`` /
|
|
122
|
+
``to_html`` for such scripts.
|
|
123
|
+
"""
|
|
124
|
+
return "\n".join(_block(self, gap)[0])
|
|
125
|
+
|
|
126
|
+
def render(self, gap: int = 3) -> str:
|
|
127
|
+
"""Alias for :meth:`to_text` (mirrors ``Derivation.render`` / ``Node.tree_str``)."""
|
|
128
|
+
return self.to_text(gap)
|
|
129
|
+
|
|
130
|
+
def to_latex(self) -> str:
|
|
131
|
+
"""Render as a ``bussproofs`` proof tree (requires ``\\usepackage{bussproofs}``)."""
|
|
132
|
+
return "\\begin{prooftree}\n" + "\n".join(_latex_lines(self)) + "\n\\end{prooftree}"
|
|
133
|
+
|
|
134
|
+
def to_html(self, title: str = "CCG derivation") -> str:
|
|
135
|
+
"""Render as a self-contained, theme-aware HTML page (ccg2lambda idiom)."""
|
|
136
|
+
return _html_page(self, title)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
# --------------------------------------------------------------------------- #
|
|
140
|
+
# Semantic composition.
|
|
141
|
+
# --------------------------------------------------------------------------- #
|
|
142
|
+
|
|
143
|
+
def _apply(functor: Optional[Node], argument: Optional[Node], eta: bool) -> Optional[Node]:
|
|
144
|
+
"""Apply ``functor`` to ``argument`` and reduce; None if either term is missing."""
|
|
145
|
+
if functor is None or argument is None:
|
|
146
|
+
return None
|
|
147
|
+
redex = Application(functor, argument)
|
|
148
|
+
return beta_eta_normalize(redex) if eta else beta_reduce(redex)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
# --------------------------------------------------------------------------- #
|
|
152
|
+
# Text prooftree renderer (block-based; premises row / bar+label / conclusion).
|
|
153
|
+
# --------------------------------------------------------------------------- #
|
|
154
|
+
|
|
155
|
+
def _term_text(term: Optional[Node]) -> str:
|
|
156
|
+
return "" if term is None else term.to_unicode_str()
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _leaf_parts(d: "CCGDerivation") -> List[str]:
|
|
160
|
+
return [p for p in (d.word, d.category, _term_text(d.term)) if p]
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _conc_parts(d: "CCGDerivation") -> List[str]:
|
|
164
|
+
return [p for p in (d.category, _term_text(d.term)) if p]
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _rect(lines: List[str]) -> Tuple[List[str], int]:
|
|
168
|
+
"""Left-justify every line to the block's max width; return (lines, width)."""
|
|
169
|
+
w = max((len(l) for l in lines), default=0)
|
|
170
|
+
return [l.ljust(w) for l in lines], w
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _block(d: "CCGDerivation", gap: int) -> Tuple[List[str], int]:
|
|
174
|
+
"""Return (lines, bar_width) for the subtree rooted at ``d``.
|
|
175
|
+
|
|
176
|
+
``bar_width`` is the width of the inference bar / conclusion region (the block's
|
|
177
|
+
lines may be wider than ``bar_width`` when a rule label overhangs to the right, so
|
|
178
|
+
a parent centres siblings on ``bar_width`` but joins them on the padded width).
|
|
179
|
+
"""
|
|
180
|
+
if not d.children:
|
|
181
|
+
parts = _leaf_parts(d) or [""]
|
|
182
|
+
w = max(len(p) for p in parts)
|
|
183
|
+
return _rect([p.center(w) for p in parts])
|
|
184
|
+
|
|
185
|
+
blocks = [_block(c, gap) for c in d.children]
|
|
186
|
+
height = max(len(lines) for lines, _ in blocks)
|
|
187
|
+
padded: List[Tuple[List[str], int]] = []
|
|
188
|
+
for lines, _ in blocks:
|
|
189
|
+
w = max(len(l) for l in lines) # full padded width
|
|
190
|
+
lines = [l.ljust(w) for l in lines]
|
|
191
|
+
lines = [" " * w] * (height - len(lines)) + lines # align bottoms
|
|
192
|
+
padded.append((lines, w))
|
|
193
|
+
|
|
194
|
+
sep = " " * gap
|
|
195
|
+
prem_lines = [sep.join(padded[i][0][r] for i in range(len(padded)))
|
|
196
|
+
for r in range(height)]
|
|
197
|
+
prem_w = max(len(l) for l in prem_lines)
|
|
198
|
+
|
|
199
|
+
conc = _conc_parts(d) or [""]
|
|
200
|
+
conc_w = max(len(p) for p in conc)
|
|
201
|
+
bar_w = max(prem_w, conc_w)
|
|
202
|
+
|
|
203
|
+
out = [l.center(bar_w) for l in prem_lines]
|
|
204
|
+
bar = "─" * bar_w + (" " + d.rule if d.rule else "")
|
|
205
|
+
out.append(bar)
|
|
206
|
+
out.extend(p.center(bar_w) for p in conc)
|
|
207
|
+
return _rect(out)[0], bar_w
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
# --------------------------------------------------------------------------- #
|
|
211
|
+
# LaTeX (bussproofs) renderer.
|
|
212
|
+
# --------------------------------------------------------------------------- #
|
|
213
|
+
|
|
214
|
+
_INF = {1: "UnaryInfC", 2: "BinaryInfC", 3: "TrinaryInfC",
|
|
215
|
+
4: "QuaternaryInfC", 5: "QuinaryInfC"}
|
|
216
|
+
|
|
217
|
+
# LaTeX-special characters in free text that must be escaped to compile.
|
|
218
|
+
_LATEX_ESC = {
|
|
219
|
+
"&": "\\&", "%": "\\%", "$": "\\$", "#": "\\#", "_": "\\_",
|
|
220
|
+
"{": "\\{", "}": "\\}", "~": "\\textasciitilde{}",
|
|
221
|
+
"^": "\\textasciicircum{}", "\\": "\\textbackslash{}",
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _latex_text(s: str) -> str:
|
|
226
|
+
"""Escape every LaTeX special in a free-text token (a surface word)."""
|
|
227
|
+
return "".join(_LATEX_ESC.get(c, c) for c in s)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _cat_latex(category: Optional[str]) -> str:
|
|
231
|
+
"""CCG category to math-mode LaTeX: the CCG slash ``\\`` becomes ``\\backslash`` and
|
|
232
|
+
``_{…}`` subscripts are kept, but compile-breaking specials (``% & # $ ^ ~``) are
|
|
233
|
+
escaped."""
|
|
234
|
+
if not category:
|
|
235
|
+
return ""
|
|
236
|
+
out = []
|
|
237
|
+
for c in category:
|
|
238
|
+
if c == "\\":
|
|
239
|
+
out.append("\\backslash ")
|
|
240
|
+
elif c in ("&", "%", "#", "$"):
|
|
241
|
+
out.append("\\" + c)
|
|
242
|
+
elif c == "~":
|
|
243
|
+
out.append("\\textasciitilde{}")
|
|
244
|
+
elif c == "^":
|
|
245
|
+
out.append("\\textasciicircum{}")
|
|
246
|
+
else: # keep letters, digits, / [ ] = _ { }
|
|
247
|
+
out.append(c)
|
|
248
|
+
return "".join(out)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _content_latex(d: "CCGDerivation") -> str:
|
|
252
|
+
"""The stacked (word / category / term) content of one node, as a math array.
|
|
253
|
+
|
|
254
|
+
The ``word`` (free text, ``\\mbox`` — base LaTeX, no amsmath needed) and the
|
|
255
|
+
``category`` are escaped here; the ``term`` is emitted via ``Node.to_latex`` and
|
|
256
|
+
assumes predicate/function names are LaTeX-safe (the parser's ``[A-Z][a-zA-Z0-9]*``
|
|
257
|
+
predicate names always are).
|
|
258
|
+
"""
|
|
259
|
+
rows: List[str] = []
|
|
260
|
+
if d.word:
|
|
261
|
+
rows.append("\\mbox{%s}" % _latex_text(d.word))
|
|
262
|
+
if d.category:
|
|
263
|
+
rows.append(_cat_latex(d.category))
|
|
264
|
+
if d.term is not None:
|
|
265
|
+
rows.append(d.term.to_latex())
|
|
266
|
+
if not rows:
|
|
267
|
+
rows.append("")
|
|
268
|
+
return "$\\begin{array}{c}" + " \\\\ ".join(rows) + "\\end{array}$"
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def _latex_lines(d: "CCGDerivation") -> List[str]:
|
|
272
|
+
"""Post-order bussproofs commands for the subtree rooted at ``d``."""
|
|
273
|
+
if not d.children:
|
|
274
|
+
return [" \\AxiomC{%s}" % _content_latex(d)]
|
|
275
|
+
lines: List[str] = []
|
|
276
|
+
for c in d.children:
|
|
277
|
+
lines += _latex_lines(c)
|
|
278
|
+
n = len(d.children)
|
|
279
|
+
if n not in _INF:
|
|
280
|
+
raise ValueError(
|
|
281
|
+
"to_latex: bussproofs supports 1-5 premises per step, got %d "
|
|
282
|
+
"(rule %r); split the step or use to_text/to_html." % (n, d.rule))
|
|
283
|
+
if d.rule:
|
|
284
|
+
lines.append(" \\RightLabel{\\scriptsize %s}" % d.rule)
|
|
285
|
+
lines.append(" \\%s{%s}" % (_INF[n], _content_latex(d)))
|
|
286
|
+
return lines
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
# --------------------------------------------------------------------------- #
|
|
290
|
+
# HTML renderer (self-contained, theme-aware, ccg2lambda idiom).
|
|
291
|
+
# --------------------------------------------------------------------------- #
|
|
292
|
+
|
|
293
|
+
def _esc_html(s: str) -> str:
|
|
294
|
+
return (s.replace("&", "&").replace("<", "<").replace(">", ">"))
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def _cat_html(category: Optional[str]) -> str:
|
|
298
|
+
if not category:
|
|
299
|
+
return ""
|
|
300
|
+
import re
|
|
301
|
+
return re.sub(r"_\{([^}]*)\}", r"<sub>\1</sub>", _esc_html(category))
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _node_html(d: "CCGDerivation") -> str:
|
|
305
|
+
if not d.children:
|
|
306
|
+
parts = []
|
|
307
|
+
if d.word:
|
|
308
|
+
parts.append('<div class="w">%s</div>' % _esc_html(d.word))
|
|
309
|
+
parts.append('<div class="c">%s</div>' % _cat_html(d.category))
|
|
310
|
+
parts.append('<div class="s">%s</div>' % _esc_html(_term_text(d.term)))
|
|
311
|
+
return '<div class="nd leaf">%s</div>' % "".join(parts)
|
|
312
|
+
prem = "".join(_node_html(c) for c in d.children)
|
|
313
|
+
rule = ('<span class="r">%s</span>' % _esc_html(d.rule)) if d.rule else ""
|
|
314
|
+
return ('<div class="nd"><div class="pr">%s</div>'
|
|
315
|
+
'<div class="bar">%s</div>'
|
|
316
|
+
'<div class="cn"><div class="c">%s</div><div class="s">%s</div></div></div>'
|
|
317
|
+
% (prem, rule, _cat_html(d.category), _esc_html(_term_text(d.term))))
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
_HTML_CSS = """
|
|
321
|
+
:root{--pg:#faf9f6;--ink:#23232a;--cat:#b02a2a;--sem:#1c46b0;--rule:#3f3f47;--bar:#2b2b31;--line:#e4e1d8}
|
|
322
|
+
@media(prefers-color-scheme:dark){:root{--pg:#15151a;--ink:#e9e8e4;--cat:#ff7d6d;--sem:#84a6ff;--rule:#c3c3cd;--bar:#d6d5d0;--line:#2c2c34}}
|
|
323
|
+
:root[data-theme=light]{--pg:#faf9f6;--ink:#23232a;--cat:#b02a2a;--sem:#1c46b0;--rule:#3f3f47;--bar:#2b2b31;--line:#e4e1d8}
|
|
324
|
+
:root[data-theme=dark]{--pg:#15151a;--ink:#e9e8e4;--cat:#ff7d6d;--sem:#84a6ff;--rule:#c3c3cd;--bar:#d6d5d0;--line:#2c2c34}
|
|
325
|
+
body{margin:0;background:var(--pg);color:var(--ink);font-family:"Times New Roman",Times,serif}
|
|
326
|
+
.scroll{overflow-x:auto;padding:26px 8px}
|
|
327
|
+
.fig{width:max-content;min-width:100%;padding:0 30px;font-size:14px}
|
|
328
|
+
.nd{display:inline-flex;flex-direction:column;align-items:center;vertical-align:bottom}
|
|
329
|
+
.pr{display:flex;align-items:flex-end;justify-content:center;gap:30px}
|
|
330
|
+
.bar{position:relative;align-self:stretch;border-top:1.3px solid var(--bar);margin-top:3px}
|
|
331
|
+
.r{position:absolute;left:100%;top:50%;transform:translateY(-50%);padding-left:5px;font-size:9.5px;font-style:italic;color:var(--rule);white-space:nowrap}
|
|
332
|
+
.cn{padding-top:2px;text-align:center}
|
|
333
|
+
.leaf{padding:0 2px}
|
|
334
|
+
.w{font-weight:700;white-space:nowrap;padding-bottom:1px}
|
|
335
|
+
.c{color:var(--cat);font-style:italic;font-size:12.5px;white-space:nowrap}
|
|
336
|
+
.c sub{font-size:.72em}
|
|
337
|
+
.s{color:var(--sem);white-space:nowrap;font-size:13px}
|
|
338
|
+
"""
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def _html_page(d: "CCGDerivation", title: str) -> str:
|
|
342
|
+
return (
|
|
343
|
+
"<!doctype html>\n<html><head><meta charset=\"utf-8\">\n"
|
|
344
|
+
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n"
|
|
345
|
+
"<title>%s</title>\n<style>%s</style></head>\n<body>\n"
|
|
346
|
+
"<div class=\"scroll\"><div class=\"fig\">%s</div></div>\n</body></html>\n"
|
|
347
|
+
% (_esc_html(title), _HTML_CSS, _node_html(d)))
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
# --------------------------------------------------------------------------- #
|
|
351
|
+
# Bridge: a beta-reduction path as a derivation.
|
|
352
|
+
# --------------------------------------------------------------------------- #
|
|
353
|
+
|
|
354
|
+
def reduction_derivation(term: Node, *, limit: int = 1000) -> CCGDerivation:
|
|
355
|
+
"""Turn the beta-reduction path of ``term`` into a :class:`CCGDerivation` chain.
|
|
356
|
+
|
|
357
|
+
The topmost premise is the original ``term``; each ``β`` step reduces it one
|
|
358
|
+
contraction further, with the beta-normal form as the bottom conclusion. Render it
|
|
359
|
+
with :meth:`~CCGDerivation.to_text` / ``to_latex`` / ``to_html`` like any
|
|
360
|
+
derivation. Raises :class:`ReductionLimitError` past ``limit`` steps.
|
|
361
|
+
"""
|
|
362
|
+
from .lambda_tools import reduce_trace
|
|
363
|
+
trace = reduce_trace(term, limit=limit)
|
|
364
|
+
node = CCGDerivation(term=trace[0]) # a bare term (no word/category)
|
|
365
|
+
for step in trace[1:]:
|
|
366
|
+
node = CCGDerivation.unary("β", None, node, term=step)
|
|
367
|
+
return node
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Dialect detection for formula text — the single source of truth.
|
|
2
|
+
|
|
3
|
+
:func:`unicode_logic_kit.api.parse_any` auto-detects which surface syntax a
|
|
4
|
+
formula text is written in before parsing it. The detection ORDER is
|
|
5
|
+
load-bearing (a Prover9 formula also contains ``->`` which the unicode
|
|
6
|
+
ladder would half-read; SMT-LIB text is full of parentheses every other
|
|
7
|
+
grammar chokes on), so it lives here as one pure, importable, testable
|
|
8
|
+
function instead of an if-cascade buried in the facade. ``parse_any``
|
|
9
|
+
consumes exactly this candidate list; nothing else defines an order.
|
|
10
|
+
|
|
11
|
+
The detectors are deliberately cheap syntactic signals (regex hits), not
|
|
12
|
+
parsers: detection NOMINATES candidates in order, the real parsers then
|
|
13
|
+
accept or reject. A signal miss therefore never loses a formula — the
|
|
14
|
+
unicode mode ladder is always the terminal catch-all — it only costs the
|
|
15
|
+
nominated dialect its early slot.
|
|
16
|
+
|
|
17
|
+
Signals (each with the concrete give-away it keys on):
|
|
18
|
+
|
|
19
|
+
* ``smtlib`` — an S-expression command head: ``(assert``,
|
|
20
|
+
``(declare-fun``, ``(declare-const``, ``(set-logic``, ``(define-fun``.
|
|
21
|
+
* ``tptp`` (annotated) — a role wrapper ``fof(`` / ``cnf(`` / ``tff(`` /
|
|
22
|
+
``thf(``.
|
|
23
|
+
* ``latex`` — a backslash command from the FOL vocabulary
|
|
24
|
+
(``\\forall``, ``\\exists``, ``\\land``, …, ``\\Box``, ``\\Diamond``).
|
|
25
|
+
* ``tptp_bare`` (ASCII only) — TPTP quantifier blocks ``![…]`` / ``?[…]``
|
|
26
|
+
or the ASCII connectives ``=>``/``<=>``/``~``.
|
|
27
|
+
* ``prover9`` (ASCII only) — ``->``/``<->``, the word quantifiers
|
|
28
|
+
``all`` / ``exists``, or ``&``/``|``.
|
|
29
|
+
* ``unicode`` — the kit's own surface syntax (mode ladder, classical
|
|
30
|
+
first); always the last candidate. Non-ASCII text that carries none of
|
|
31
|
+
the above signals goes STRAIGHT here — the two ASCII-only detectors are
|
|
32
|
+
skipped because any unicode connective already disambiguates.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
import re
|
|
36
|
+
from typing import Tuple
|
|
37
|
+
|
|
38
|
+
__all__ = ["detect_dialects", "DIALECT_SIGNALS"]
|
|
39
|
+
|
|
40
|
+
_SMT_RE = re.compile(r"\(\s*(assert|declare-fun|declare-const|set-logic|define-fun)\b")
|
|
41
|
+
_TPTP_ANNOTATED_RE = re.compile(r"\b(fof|cnf|tff|thf)\s*\(")
|
|
42
|
+
_TPTP_BARE_RE = re.compile(r"(!\s*\[|\?\s*\[|=>|<=>|~)")
|
|
43
|
+
_LATEX_RE = re.compile(
|
|
44
|
+
r"\\(forall|exists|land|lor|lnot|neg|rightarrow|to|leftrightarrow|Box|Diamond)\b")
|
|
45
|
+
_PROVER9_RE = re.compile(r"(->|<->)|(\ball\s)|(\bexists\s)|(&)|(\|)")
|
|
46
|
+
|
|
47
|
+
#: (dialect name, signal regex, ascii_only) in nomination order — the
|
|
48
|
+
#: importable form of the table in the module docstring.
|
|
49
|
+
DIALECT_SIGNALS: Tuple[Tuple[str, "re.Pattern[str]", bool], ...] = (
|
|
50
|
+
("smtlib", _SMT_RE, False),
|
|
51
|
+
("tptp", _TPTP_ANNOTATED_RE, False),
|
|
52
|
+
("latex", _LATEX_RE, False),
|
|
53
|
+
("tptp_bare", _TPTP_BARE_RE, True),
|
|
54
|
+
("prover9", _PROVER9_RE, True),
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def detect_dialects(text: str) -> Tuple[str, ...]:
|
|
59
|
+
"""The ORDERED dialect candidates for ``text``, ``"unicode"`` last.
|
|
60
|
+
|
|
61
|
+
Pure and side-effect free: no parser runs. ``parse_any`` tries the
|
|
62
|
+
returned candidates front to back and falls through on each parse
|
|
63
|
+
failure, so the order here IS the facade's detection order. The result
|
|
64
|
+
always ends in ``"unicode"`` and never contains duplicates.
|
|
65
|
+
"""
|
|
66
|
+
is_ascii = text.isascii()
|
|
67
|
+
candidates = [name for name, signal, ascii_only in DIALECT_SIGNALS
|
|
68
|
+
if (is_ascii or not ascii_only) and signal.search(text)]
|
|
69
|
+
candidates.append("unicode")
|
|
70
|
+
return tuple(candidates)
|