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.
Files changed (237) hide show
  1. unicode_logic_kit/__init__.py +385 -0
  2. unicode_logic_kit/__main__.py +520 -0
  3. unicode_logic_kit/_deadline.py +219 -0
  4. unicode_logic_kit/ace/__init__.py +126 -0
  5. unicode_logic_kit/ace/_align.py +135 -0
  6. unicode_logic_kit/ace/chem_lexicon.py +128 -0
  7. unicode_logic_kit/ace/drs_reader.py +570 -0
  8. unicode_logic_kit/ace/mapping.py +666 -0
  9. unicode_logic_kit/ace/reverse_modal.py +138 -0
  10. unicode_logic_kit/ace/runner.py +551 -0
  11. unicode_logic_kit/ace/translate.py +452 -0
  12. unicode_logic_kit/ace/verbalize.py +1070 -0
  13. unicode_logic_kit/api.py +1284 -0
  14. unicode_logic_kit/atp/__init__.py +177 -0
  15. unicode_logic_kit/atp/_ascii_names.py +113 -0
  16. unicode_logic_kit/atp/_html.py +72 -0
  17. unicode_logic_kit/atp/_substructural_input.py +228 -0
  18. unicode_logic_kit/atp/_tff_problem.py +715 -0
  19. unicode_logic_kit/atp/_tptp_problem.py +1111 -0
  20. unicode_logic_kit/atp/_writer_support.py +289 -0
  21. unicode_logic_kit/atp/clingo_backend.py +1180 -0
  22. unicode_logic_kit/atp/cvc5_backend.py +1385 -0
  23. unicode_logic_kit/atp/eprover_backend.py +732 -0
  24. unicode_logic_kit/atp/finite_domain.py +1055 -0
  25. unicode_logic_kit/atp/fitch.py +1547 -0
  26. unicode_logic_kit/atp/fitch_search.py +551 -0
  27. unicode_logic_kit/atp/hets_backend.py +339 -0
  28. unicode_logic_kit/atp/hybrid_down.py +120 -0
  29. unicode_logic_kit/atp/incremental.py +250 -0
  30. unicode_logic_kit/atp/kripke_enum.py +741 -0
  31. unicode_logic_kit/atp/lambek.py +436 -0
  32. unicode_logic_kit/atp/leo3_backend.py +332 -0
  33. unicode_logic_kit/atp/linear.py +738 -0
  34. unicode_logic_kit/atp/lj.py +705 -0
  35. unicode_logic_kit/atp/logic_backends.py +566 -0
  36. unicode_logic_kit/atp/ltl_tableau.py +1084 -0
  37. unicode_logic_kit/atp/minizinc_backend.py +1402 -0
  38. unicode_logic_kit/atp/modal_tableau.py +1382 -0
  39. unicode_logic_kit/atp/nanocop_backend.py +410 -0
  40. unicode_logic_kit/atp/portfolio.py +489 -0
  41. unicode_logic_kit/atp/protocol.py +1803 -0
  42. unicode_logic_kit/atp/prover9_entailment.py +1153 -0
  43. unicode_logic_kit/atp/resolution.py +1376 -0
  44. unicode_logic_kit/atp/resolution_check.py +1114 -0
  45. unicode_logic_kit/atp/sequent.py +1050 -0
  46. unicode_logic_kit/atp/tableau.py +921 -0
  47. unicode_logic_kit/atp/tableau_check.py +543 -0
  48. unicode_logic_kit/atp/tptp_ncl.py +811 -0
  49. unicode_logic_kit/atp/tptp_tff.py +1546 -0
  50. unicode_logic_kit/atp/tstp.py +1333 -0
  51. unicode_logic_kit/atp/tstp_check.py +1096 -0
  52. unicode_logic_kit/atp/twee_backend.py +236 -0
  53. unicode_logic_kit/atp/twee_check.py +711 -0
  54. unicode_logic_kit/atp/twee_entailment.py +953 -0
  55. unicode_logic_kit/atp/vampire_entailment.py +540 -0
  56. unicode_logic_kit/atp/z3_arith.py +470 -0
  57. unicode_logic_kit/atp/z3_equivalence.py +36 -0
  58. unicode_logic_kit/atp/z3_fuzzy.py +362 -0
  59. unicode_logic_kit/atp/z3_input.py +500 -0
  60. unicode_logic_kit/atp/z3_models.py +208 -0
  61. unicode_logic_kit/chem/__init__.py +88 -0
  62. unicode_logic_kit/chem/_naming.py +284 -0
  63. unicode_logic_kit/chem/cache.py +185 -0
  64. unicode_logic_kit/chem/interop.py +244 -0
  65. unicode_logic_kit/chem/mol.py +525 -0
  66. unicode_logic_kit/chem/signature.py +112 -0
  67. unicode_logic_kit/comorphism.py +497 -0
  68. unicode_logic_kit/dl/__init__.py +384 -0
  69. unicode_logic_kit/dl/classification.py +227 -0
  70. unicode_logic_kit/dl/concepts.py +632 -0
  71. unicode_logic_kit/dl/datatypes.py +818 -0
  72. unicode_logic_kit/dl/owl_functional.py +2433 -0
  73. unicode_logic_kit/dl/owl_manchester.py +1637 -0
  74. unicode_logic_kit/dl/owl_reasoner.py +790 -0
  75. unicode_logic_kit/dl/parser.py +391 -0
  76. unicode_logic_kit/dl/tableau.py +4048 -0
  77. unicode_logic_kit/dl/translate.py +2704 -0
  78. unicode_logic_kit/drt/__init__.py +94 -0
  79. unicode_logic_kit/drt/export.py +179 -0
  80. unicode_logic_kit/drt/nodes.py +506 -0
  81. unicode_logic_kit/drt/parser.py +965 -0
  82. unicode_logic_kit/drt/resolve.py +195 -0
  83. unicode_logic_kit/drt/reverse.py +175 -0
  84. unicode_logic_kit/eval/__init__.py +106 -0
  85. unicode_logic_kit/eval/batch.py +382 -0
  86. unicode_logic_kit/eval/canonical.py +663 -0
  87. unicode_logic_kit/eval/chem_batch.py +606 -0
  88. unicode_logic_kit/eval/converses.py +200 -0
  89. unicode_logic_kit/eval/datasets/__init__.py +136 -0
  90. unicode_logic_kit/eval/datasets/_base.py +263 -0
  91. unicode_logic_kit/eval/datasets/_proofwriter_proof.py +422 -0
  92. unicode_logic_kit/eval/datasets/c3po.py +678 -0
  93. unicode_logic_kit/eval/datasets/folio.py +158 -0
  94. unicode_logic_kit/eval/datasets/fracas.py +418 -0
  95. unicode_logic_kit/eval/datasets/groves.py +191 -0
  96. unicode_logic_kit/eval/datasets/logicbench.py +467 -0
  97. unicode_logic_kit/eval/datasets/logicnli.py +303 -0
  98. unicode_logic_kit/eval/datasets/malls.py +133 -0
  99. unicode_logic_kit/eval/datasets/pfolio.py +594 -0
  100. unicode_logic_kit/eval/datasets/pmb.py +242 -0
  101. unicode_logic_kit/eval/datasets/prontoqa.py +611 -0
  102. unicode_logic_kit/eval/datasets/proofwriter.py +1431 -0
  103. unicode_logic_kit/eval/datasets/proverqa.py +674 -0
  104. unicode_logic_kit/eval/datasets/willow.py +478 -0
  105. unicode_logic_kit/eval/equivalence.py +466 -0
  106. unicode_logic_kit/eval/exercise_gen.py +533 -0
  107. unicode_logic_kit/eval/explain.py +791 -0
  108. unicode_logic_kit/eval/generality.py +750 -0
  109. unicode_logic_kit/eval/metric_hf.py +458 -0
  110. unicode_logic_kit/eval/predicate_match.py +343 -0
  111. unicode_logic_kit/eval/theory_check.py +1170 -0
  112. unicode_logic_kit/eval/validate.py +306 -0
  113. unicode_logic_kit/fol/__init__.py +177 -0
  114. unicode_logic_kit/fol/_atom_keys.py +510 -0
  115. unicode_logic_kit/fol/_fol_nodes.py +3586 -0
  116. unicode_logic_kit/fol/_free_parameters.py +105 -0
  117. unicode_logic_kit/fol/_ho_nodes.py +448 -0
  118. unicode_logic_kit/fol/_hybrid_nodes.py +308 -0
  119. unicode_logic_kit/fol/_identifiers.py +1091 -0
  120. unicode_logic_kit/fol/_lambek_nodes.py +112 -0
  121. unicode_logic_kit/fol/_linear_nodes.py +352 -0
  122. unicode_logic_kit/fol/_modal_nodes.py +1467 -0
  123. unicode_logic_kit/fol/_msfl_nodes.py +2196 -0
  124. unicode_logic_kit/fol/_numeral_symbols.py +231 -0
  125. unicode_logic_kit/fol/_so_nodes.py +200 -0
  126. unicode_logic_kit/fol/_symbol_names.py +81 -0
  127. unicode_logic_kit/fol/_team_nodes.py +181 -0
  128. unicode_logic_kit/fol/_tptp_symbols.py +551 -0
  129. unicode_logic_kit/fol/_truth_constants.py +117 -0
  130. unicode_logic_kit/fol/casl_export.py +1135 -0
  131. unicode_logic_kit/fol/casl_import.py +929 -0
  132. unicode_logic_kit/fol/derivation.py +367 -0
  133. unicode_logic_kit/fol/dialect_detect.py +70 -0
  134. unicode_logic_kit/fol/dialect_repair.py +537 -0
  135. unicode_logic_kit/fol/frames.py +637 -0
  136. unicode_logic_kit/fol/grammars/terminals.lark +31 -0
  137. unicode_logic_kit/fol/lambda_tools.py +297 -0
  138. unicode_logic_kit/fol/latex_input.py +429 -0
  139. unicode_logic_kit/fol/modal_translation.py +944 -0
  140. unicode_logic_kit/fol/msflparser.py +1033 -0
  141. unicode_logic_kit/fol/naming.py +422 -0
  142. unicode_logic_kit/fol/nodes.py +241 -0
  143. unicode_logic_kit/fol/normalforms.py +492 -0
  144. unicode_logic_kit/fol/pal.py +287 -0
  145. unicode_logic_kit/fol/prolog_export.py +566 -0
  146. unicode_logic_kit/fol/prolog_input.py +505 -0
  147. unicode_logic_kit/fol/prover9_input.py +1325 -0
  148. unicode_logic_kit/fol/qml.py +1760 -0
  149. unicode_logic_kit/fol/qmltp_input.py +525 -0
  150. unicode_logic_kit/fol/sanitize.py +221 -0
  151. unicode_logic_kit/fol/serialize.py +79 -0
  152. unicode_logic_kit/fol/signature.py +1290 -0
  153. unicode_logic_kit/fol/simplify_check.py +544 -0
  154. unicode_logic_kit/fol/spans.py +594 -0
  155. unicode_logic_kit/fol/tptp_input.py +1503 -0
  156. unicode_logic_kit/fol/tptp_repair.py +941 -0
  157. unicode_logic_kit/fol/unification.py +157 -0
  158. unicode_logic_kit/fol/verbalize.py +263 -0
  159. unicode_logic_kit/hets/__init__.py +163 -0
  160. unicode_logic_kit/hets/bridge.py +142 -0
  161. unicode_logic_kit/hets/client.py +748 -0
  162. unicode_logic_kit/hets/docker.py +420 -0
  163. unicode_logic_kit/hets/dol.py +712 -0
  164. unicode_logic_kit/hets/haskell_json.py +355 -0
  165. unicode_logic_kit/hets/owl_backend.py +794 -0
  166. unicode_logic_kit/hets/owl_cli.py +598 -0
  167. unicode_logic_kit/hets/symbols.py +512 -0
  168. unicode_logic_kit/hol/__init__.py +140 -0
  169. unicode_logic_kit/hol/_ho_common.py +323 -0
  170. unicode_logic_kit/hol/_isabelle_binders.py +125 -0
  171. unicode_logic_kit/hol/classical.py +812 -0
  172. unicode_logic_kit/hol/deepshallow/__init__.py +45 -0
  173. unicode_logic_kit/hol/deepshallow/_common.py +177 -0
  174. unicode_logic_kit/hol/deepshallow/conditional.py +225 -0
  175. unicode_logic_kit/hol/deepshallow/intuitionistic.py +181 -0
  176. unicode_logic_kit/hol/deepshallow/modal.py +217 -0
  177. unicode_logic_kit/hol/deepshallow/qml.py +406 -0
  178. unicode_logic_kit/hol/deepshallow/relevant.py +206 -0
  179. unicode_logic_kit/hol/free.py +753 -0
  180. unicode_logic_kit/hol/goedel.py +336 -0
  181. unicode_logic_kit/hol/ho_modal.py +1743 -0
  182. unicode_logic_kit/hol/intuitionistic.py +403 -0
  183. unicode_logic_kit/hol/isabelle_conditional.py +593 -0
  184. unicode_logic_kit/hol/isabelle_modal.py +1908 -0
  185. unicode_logic_kit/hol/isabelle_relevant.py +412 -0
  186. unicode_logic_kit/hol/isabelle_runner.py +1147 -0
  187. unicode_logic_kit/hol/isabelle_substructural.py +884 -0
  188. unicode_logic_kit/hol/lean.py +1018 -0
  189. unicode_logic_kit/hol/manyvalued.py +921 -0
  190. unicode_logic_kit/hol/secondorder.py +687 -0
  191. unicode_logic_kit/hol/thf_modal.py +941 -0
  192. unicode_logic_kit/hol/thirdorder.py +397 -0
  193. unicode_logic_kit/ilp/__init__.py +89 -0
  194. unicode_logic_kit/ilp/readback.py +389 -0
  195. unicode_logic_kit/ilp/separation.py +153 -0
  196. unicode_logic_kit/ilp/task.py +730 -0
  197. unicode_logic_kit/logic.py +163 -0
  198. unicode_logic_kit/mcp/__init__.py +28 -0
  199. unicode_logic_kit/mcp/__main__.py +5 -0
  200. unicode_logic_kit/mcp/chem_tools.py +1031 -0
  201. unicode_logic_kit/mcp/server.py +2453 -0
  202. unicode_logic_kit/mcp/syntax_spec.py +681 -0
  203. unicode_logic_kit/prob/__init__.py +53 -0
  204. unicode_logic_kit/prob/_bdd.py +225 -0
  205. unicode_logic_kit/prob/_column_gen.py +668 -0
  206. unicode_logic_kit/prob/distribution.py +686 -0
  207. unicode_logic_kit/prob/nilsson.py +470 -0
  208. unicode_logic_kit/py.typed +0 -0
  209. unicode_logic_kit/semantics/__init__.py +137 -0
  210. unicode_logic_kit/semantics/_modal_reject.py +156 -0
  211. unicode_logic_kit/semantics/action_models.py +466 -0
  212. unicode_logic_kit/semantics/asp_models.py +1200 -0
  213. unicode_logic_kit/semantics/conditional.py +580 -0
  214. unicode_logic_kit/semantics/dynamic_epistemic.py +95 -0
  215. unicode_logic_kit/semantics/free_logic.py +913 -0
  216. unicode_logic_kit/semantics/fuzzy.py +384 -0
  217. unicode_logic_kit/semantics/fuzzy_kripke.py +442 -0
  218. unicode_logic_kit/semantics/intuitionistic.py +581 -0
  219. unicode_logic_kit/semantics/kripke.py +1139 -0
  220. unicode_logic_kit/semantics/manyvalued.py +580 -0
  221. unicode_logic_kit/semantics/matrix.py +342 -0
  222. unicode_logic_kit/semantics/model_eval.py +1135 -0
  223. unicode_logic_kit/semantics/modelfinder.py +1036 -0
  224. unicode_logic_kit/semantics/nonmonotonic.py +372 -0
  225. unicode_logic_kit/semantics/relevant.py +331 -0
  226. unicode_logic_kit/semantics/secondorder.py +657 -0
  227. unicode_logic_kit/semantics/structures.py +352 -0
  228. unicode_logic_kit/semantics/tarski.py +975 -0
  229. unicode_logic_kit/semantics/team.py +315 -0
  230. unicode_logic_kit/semantics/team_translation.py +416 -0
  231. unicode_logic_kit/semantics/thirdorder.py +358 -0
  232. unicode_logic_kit/semantics/tnorm.py +85 -0
  233. unicode_logic_kit/semantics/truthtable.py +201 -0
  234. unicode_logic_kit-0.31.0.dist-info/METADATA +333 -0
  235. unicode_logic_kit-0.31.0.dist-info/RECORD +237 -0
  236. unicode_logic_kit-0.31.0.dist-info/WHEEL +4 -0
  237. 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("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;"))
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)