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,525 @@
1
+ """QMLTP input: read QMLTP (Quantified Modal Logic Theorem Proving library) ``.p``
2
+ problems into the kit AST.
3
+
4
+ QMLTP (Raths & Otten, "Building a Problem Library for First-Order Modal
5
+ Logics", TABLEAUX 2009; Raths & Otten, "The QMLTP Problem Library for
6
+ First-Order Modal Logics", CADE 2012) is a 600-problem benchmark for
7
+ first-order modal logic, in an extension of plain TPTP ``fof`` syntax. This
8
+ is a SEPARATE, only loosely-related dialect from NXF (see
9
+ :mod:`unicode_logic_kit.atp.tptp_ncl`): QMLTP predates NXF, uses its own
10
+ statement keyword and unary modal connectives, and records a problem's
11
+ per-logic/per-domain-regime status in a structured comment header rather than
12
+ NXF's ``$modal``/``$domains`` logic-specification statement.
13
+
14
+ Verified DIRECTLY against the real library (downloaded and inspected
15
+ 2026-09-18 from ``https://www.iltp.de/qmltp/download/QMLTP-v1.1.tar.gz`` —
16
+ see "License" below) before writing a line of this module, rather than
17
+ assumed from the roadmap description that named this module, which turned
18
+ out to describe QMLTP's syntax and header format INCORRECTLY in two ways
19
+ corrected here (see the deviations noted at each point):
20
+
21
+ * **Statement keyword is ``qmf``, not ``fof``.** Every QMLTP problem file's
22
+ formula statements read ``qmf(name, role, formula).`` — literally,
23
+ verbatim, in all 600 problems (confirmed: ``grep -c '^qmf('`` across the
24
+ whole library's ``Problems/`` tree returns exactly 600 hits, zero for
25
+ ``^fof(``). This reader therefore accepts ONLY ``qmf(...)``, refusing
26
+ ``fof``/``cnf``/``tff`` (and QMLTP's own ``tpi(...)`` — see below) by
27
+ name.
28
+ * **The modal connectives are ``#box``/``#dia`` (a ``#``-prefixed keyword
29
+ applied with ``:``), not the ``box:(...)``/``dia:(...)`` (no ``#``) the
30
+ roadmap item that named this module described** — e.g. SYM002+1.p's
31
+ conjecture reads ``( (#box : ( ! [X] : ( f(X) ) ) ) => ( ! [X] : (#box :
32
+ ( f(X) ) ) ) )``. This is the syntax the grammar extension below actually
33
+ implements.
34
+ * **Multi-modal problems (the ``MML`` domain, 20 of the 600) use a
35
+ DIFFERENT top-level statement, ``tpi(1,set_logic,modal([...],[(name,
36
+ system), ...])).``, to declare one or more NAMED modalities before using
37
+ indexed connectives ``#box(name)``/``#dia(name)`` for each** — e.g.
38
+ ``#box(fool)``, ``#box(a)``. This is itself a genuine QMLTP construct
39
+ (unlike the roadmap's guess of a NXF-style integer index), but its
40
+ ``set_logic`` payload (``modal([cumulative,rigid,local], [(fool,s4),
41
+ (a,s4), ...])``) does not fit this reader's reused ``fof``/``tff`` term
42
+ grammar (it needs bracketed lists and name/system pairs as TERMS, which
43
+ plain TPTP terms are not) — refused by name, see "Scope" below, rather
44
+ than guessed at.
45
+
46
+ Public API: :func:`parse_qmltp_formula` (one bare formula), :func:`parse_qmltp`
47
+ / :func:`load_qmltp` (a whole problem: its formulas AND its parsed header
48
+ metadata, combined — see "Deviation from ``fol.tptp_input``'s shape" below),
49
+ and the :class:`QmltpFormula` / :class:`QmltpHeader` / :class:`QmltpStatus` /
50
+ :class:`QmltpProblem` records they return.
51
+
52
+ Grammar reuse (per this item's own instructions: reuse
53
+ :mod:`unicode_logic_kit.fol.tptp_input`'s Lark grammar rather than rewrite
54
+ one). :mod:`fol.tptp_input`'s ``stmt`` production does not hard-code the
55
+ statement keyword in the GRAMMAR at all — ``LOWER "(" fof_name "," LOWER ","
56
+ formula ...")" "."`` accepts any lower-case keyword, and it is
57
+ ``_TptpTransformer.stmt`` that checks for ``fof``/``cnf``/``tff`` — so
58
+ ``qmf(...)`` already parses under the UNMODIFIED base grammar; this module's
59
+ :data:`_GRAMMAR` only ADDS four new ``?unary`` alternatives (``#box:(...)``,
60
+ ``#dia:(...)``, and the indexed forms, refused by name once recognised — see
61
+ above) to :data:`fol.tptp_input._GRAMMAR`'s own text, and
62
+ :class:`_QmltpTransformer` SUBCLASSES :class:`fol.tptp_input._TptpTransformer`,
63
+ inheriting every classical connective/quantifier/atom/term rule UNCHANGED and
64
+ overriding only ``stmt`` (accept ``qmf``, not ``fof``/``cnf``/``tff``),
65
+ ``stmt_type``/``include_stmt`` (QMLTP has neither TFF type declarations nor
66
+ ``include(...)`` — refused by name), and the four new modal rules. This
67
+ reader can therefore never disagree with :mod:`fol.tptp_input` about how to
68
+ read a classical connective, quantifier or term — the SAME code decides both.
69
+
70
+ Scope — the mono-modal alethic fragment only, matching
71
+ :mod:`unicode_logic_kit.atp.tptp_ncl`'s own writer-side boundary (see that
72
+ module's docstring):
73
+
74
+ * Supported: ``qmf(name, role, formula).`` statements over
75
+ ¬/∧/∨/→/↔/=/!=, ``!``/``?`` quantifiers (UNSORTED — no QMLTP problem in
76
+ the library uses a typed quantifier; every one reads plain ``! [X] :
77
+ ...``), and the unindexed ``#box``/``#dia`` connectives.
78
+ * NOT supported, each raising :class:`NotImplementedError` naming the
79
+ construct (never silently dropped or approximated):
80
+ - ``tpi(...)`` multi-modal ``set_logic`` directives, and the indexed
81
+ ``#box(name)``/``#dia(name)`` connectives that depend on one (the
82
+ ``MML`` domain) — see "the roadmap's guess" note above.
83
+ - ``include(...)`` directives and ``tff(...,type,...)`` declarations —
84
+ neither construct occurs anywhere in the library, so both are refused
85
+ rather than silently mis-parsed if one ever appeared.
86
+
87
+ License
88
+ -------
89
+ No redistribution licence for the QMLTP library itself could be found (as of
90
+ 2026-09-18, neither the v1.1 archive ``QMLTP-v1.1.tar.gz`` nor
91
+ ``https://www.iltp.de/qmltp/`` states one). This module therefore ships no
92
+ QMLTP file: it was developed and checked against the real v1.1 problems, and
93
+ the test suite uses small stand-ins written for it in the same syntax and
94
+ header layout (``tests/fixtures/qmltp``), each with a hand-derived status
95
+ table re-checked against :func:`~unicode_logic_kit.fol.qml.qml_is_valid`. Point
96
+ :func:`load_qmltp` at your own copy of the library to read real problems.
97
+ """
98
+
99
+ import re
100
+ from dataclasses import dataclass
101
+ from typing import Optional, Tuple
102
+
103
+ from lark import Lark
104
+ from lark.exceptions import VisitError
105
+
106
+ from .nodes import Node, Box, Diamond
107
+ from .naming import ParsingError
108
+ from .tptp_input import (
109
+ _GRAMMAR as _TPTP_GRAMMAR, _TptpTransformer, _functor_name,
110
+ )
111
+
112
+ __all__ = [
113
+ "QmltpParsingError", "QmltpFormula", "QmltpStatus", "QmltpHeader",
114
+ "QmltpProblem", "parse_qmltp_formula", "parse_qmltp", "load_qmltp",
115
+ ]
116
+
117
+
118
+ class QmltpParsingError(ParsingError):
119
+ """A QMLTP import failure, carrying a plain message.
120
+
121
+ The exact mirror of :class:`fol.tptp_input.TptpParsingError` — subclasses
122
+ :class:`ParsingError` (so ``except ParsingError`` still catches it) but
123
+ takes a string instead of a Lark exception.
124
+ """
125
+
126
+ def __init__(self, message: str):
127
+ self.args = (message,)
128
+
129
+ def __str__(self):
130
+ return self.args[0]
131
+
132
+
133
+ # =============================================================================
134
+ # Grammar: fol.tptp_input's own grammar text, extended with #box/#dia.
135
+ # =============================================================================
136
+
137
+ # The exact anchor line fol.tptp_input._GRAMMAR uses for its "~" (negation)
138
+ # alternative -- the new QMLTP modal alternatives are inserted right after
139
+ # it. A drift guard (the count() check below) turns a future, unrelated
140
+ # change to that grammar text into a loud ImportError here rather than a
141
+ # silent "#box/#dia never actually parse" failure.
142
+ _UNARY_ANCHOR = '?unary: "~" unary -> neg\n'
143
+ _QMLTP_UNARY_EXTRA = (
144
+ ' | "#box" ":" "(" formula ")" -> box\n'
145
+ ' | "#dia" ":" "(" formula ")" -> dia\n'
146
+ ' | "#box" "(" LOWER ")" ":" "(" formula ")" -> box_indexed\n'
147
+ ' | "#dia" "(" LOWER ")" ":" "(" formula ")" -> dia_indexed\n'
148
+ )
149
+
150
+ if _TPTP_GRAMMAR.count(_UNARY_ANCHOR) != 1:
151
+ raise ImportError(
152
+ "fol.qmltp_input: fol.tptp_input's grammar text no longer contains "
153
+ "the exact anchor line this module extends ('?unary: \"~\" unary "
154
+ "-> neg') -- fol.tptp_input.py must have changed; update "
155
+ "_UNARY_ANCHOR in fol/qmltp_input.py to match.")
156
+
157
+ _GRAMMAR = _TPTP_GRAMMAR.replace(_UNARY_ANCHOR, _UNARY_ANCHOR + _QMLTP_UNARY_EXTRA, 1)
158
+
159
+
160
+ class _QmltpTransformer(_TptpTransformer):
161
+ """Extends :class:`fol.tptp_input._TptpTransformer` with QMLTP's own
162
+ statement keyword and modal connectives.
163
+
164
+ Every classical rule (connectives, quantifiers, atoms, terms) is
165
+ inherited UNCHANGED — only the five methods below are new or overridden;
166
+ see the module docstring's "Grammar reuse" section.
167
+ """
168
+
169
+ def stmt(self, items):
170
+ """``qmf(name, role, formula).`` -> :class:`QmltpFormula`.
171
+
172
+ Anything else at the top level (``fof``/``cnf``/``tff``, or
173
+ QMLTP's own ``tpi(...)`` multi-modal directive — which this
174
+ base grammar cannot even fully parse, see the module docstring's
175
+ "Statement keyword" note, but a short ``tpi(name,...)`` prefix
176
+ CAN reach this far before its payload fails to match `formula`)
177
+ is refused BY NAME.
178
+ """
179
+ keyword = str(items[0])
180
+ if keyword == "tpi":
181
+ raise NotImplementedError(
182
+ "qmltp_input: 'tpi(...)' multi-modal set_logic directives "
183
+ "(the MML domain) are outside this reader's supported "
184
+ "mono-modal fragment -- see module docstring 'Scope'.")
185
+ if keyword != "qmf":
186
+ raise QmltpParsingError(
187
+ f"SYNTAX_ERROR: only 'qmf(...)' statements are QMLTP "
188
+ f"formulas (got '{keyword}(...)').")
189
+ name, role, formula = str(items[1]), str(items[2]), items[3]
190
+ return QmltpFormula(name, role, formula)
191
+
192
+ def stmt_type(self, items):
193
+ raise QmltpParsingError(
194
+ "SYNTAX_ERROR: a TFF-style type declaration "
195
+ "('name,type,...') is not part of the QMLTP dialect -- no "
196
+ "problem in the library uses one.")
197
+
198
+ def include_stmt(self, items):
199
+ raise QmltpParsingError(
200
+ "SYNTAX_ERROR: include(...) directives are not supported by "
201
+ "this QMLTP reader -- no problem in the library uses one.")
202
+
203
+ def box(self, items):
204
+ return Box(items[0])
205
+
206
+ def dia(self, items):
207
+ return Diamond(items[0])
208
+
209
+ def box_indexed(self, items):
210
+ name = _functor_name(items[0])
211
+ raise NotImplementedError(
212
+ f"qmltp_input: indexed multi-modal '#box({name})' is outside "
213
+ "this reader's supported mono-modal alethic fragment -- see "
214
+ "module docstring 'Scope'. Only the unindexed #box/#dia are "
215
+ "supported in this release.")
216
+
217
+ def dia_indexed(self, items):
218
+ name = _functor_name(items[0])
219
+ raise NotImplementedError(
220
+ f"qmltp_input: indexed multi-modal '#dia({name})' is outside "
221
+ "this reader's supported mono-modal alethic fragment -- see "
222
+ "module docstring 'Scope'. Only the unindexed #box/#dia are "
223
+ "supported in this release.")
224
+
225
+
226
+ _FORMULA_PARSER = Lark(_GRAMMAR, start="formula", parser="earley")
227
+ _FILE_PARSER = Lark(_GRAMMAR, start="file", parser="earley")
228
+ _TRANSFORMER = _QmltpTransformer()
229
+
230
+
231
+ def _parse(text: str, parser: Lark, what: str):
232
+ """Parse + transform with unified error handling (unwrapping lark's
233
+ VisitError) -- the exact mirror of fol.tptp_input._parse."""
234
+ try:
235
+ tree = parser.parse(text)
236
+ except QmltpParsingError:
237
+ raise
238
+ except Exception as exc:
239
+ raise QmltpParsingError(f"SYNTAX_ERROR: could not parse QMLTP {what}: {exc}")
240
+ try:
241
+ return _TRANSFORMER.transform(tree)
242
+ except VisitError as exc:
243
+ original = exc.orig_exc
244
+ if isinstance(original, (ParsingError, NotImplementedError)):
245
+ raise original
246
+ raise QmltpParsingError(f"SYNTAX_ERROR: in QMLTP {what}: {original}")
247
+
248
+
249
+ # =============================================================================
250
+ # Formula / header records
251
+ # =============================================================================
252
+
253
+ @dataclass(frozen=True)
254
+ class QmltpFormula:
255
+ """One annotated QMLTP statement: its ``name``, ``role``, and parsed ``formula``."""
256
+
257
+ name: str
258
+ role: str
259
+ formula: Node
260
+
261
+
262
+ @dataclass(frozen=True)
263
+ class QmltpStatus:
264
+ """One ``(logic, domain) -> verdict`` cell from a QMLTP ``% Status`` table.
265
+
266
+ ``logic`` is one of ``"K"``, ``"D"``, ``"T"``, ``"S4"``, ``"S5"``
267
+ (the literal row label QMLTP's header prints — matching
268
+ :mod:`unicode_logic_kit.fol.qml`'s own ``frame=`` names one-to-one, so a
269
+ cell reads straight into ``qml_is_valid(..., frame=status.logic)``);
270
+ ``domain`` is one of ``"varying"``, ``"cumulative"``, ``"constant"``
271
+ (the literal column label — matching ``qml_is_valid``'s ``mode=`` names,
272
+ since ``"cumulative"`` is itself one of :mod:`fol.qml`'s accepted mode
273
+ spellings, an alias of ``"increasing"``); ``verdict`` is the literal
274
+ recorded status string, kept AS-IS (``"Theorem"``, ``"Non-Theorem"``,
275
+ or occasionally ``"Unsolved"``/``"Open"``), never normalised or
276
+ validated against a fixed vocabulary.
277
+ """
278
+
279
+ logic: str
280
+ domain: str
281
+ verdict: str
282
+
283
+
284
+ @dataclass(frozen=True)
285
+ class QmltpHeader:
286
+ """Metadata recovered from a QMLTP problem file's ``%`` comment header.
287
+
288
+ ``file`` / ``domain`` / ``problem`` / ``source`` are ``None`` when the
289
+ corresponding ``% <Field> : ...`` line is absent. ``statuses`` holds
290
+ every ``(logic, domain) -> verdict`` cell of the ``% Status`` table (see
291
+ :class:`QmltpStatus`), in the file's own row/column order; empty if no
292
+ such table was found (e.g. a multi-modal problem's single-line ``%
293
+ Status : Theorem`` form, which this reader does not parse -- see module
294
+ docstring 'Scope', since multi-modal problems are refused before a
295
+ header lookup would matter). ``comments`` holds every raw ``%`` line
296
+ verbatim (stripped of its trailing newline), in source order.
297
+ """
298
+
299
+ file: Optional[str]
300
+ domain: Optional[str]
301
+ problem: Optional[str]
302
+ source: Optional[str]
303
+ statuses: Tuple[QmltpStatus, ...]
304
+ comments: Tuple[str, ...]
305
+
306
+ def status_for(self, logic: str, domain: str) -> str:
307
+ """The recorded verdict for ``(logic, domain)``, e.g. ``status_for("K",
308
+ "constant")`` -> ``"Theorem"``.
309
+
310
+ Raises:
311
+ KeyError: no cell for that exact ``(logic, domain)`` pair was
312
+ recorded (an unknown logic/domain name, or a header this
313
+ reader could not parse a status table from at all).
314
+ """
315
+ for status in self.statuses:
316
+ if status.logic == logic and status.domain == domain:
317
+ return status.verdict
318
+ available = sorted({(s.logic, s.domain) for s in self.statuses})
319
+ raise KeyError(
320
+ f"qmltp_input: no recorded status for logic={logic!r}, "
321
+ f"domain={domain!r} in this header (available: {available}).")
322
+
323
+
324
+ @dataclass(frozen=True)
325
+ class QmltpProblem:
326
+ """A whole QMLTP problem: its parsed ``formulas`` plus the recovered ``header``.
327
+
328
+ Deviation from :mod:`fol.tptp_input`'s shape: that module splits "just
329
+ the formulas" (:func:`~fol.tptp_input.parse_tptp`, a bare list) from
330
+ "formulas plus header" (:func:`~fol.tptp_input.parse_tptp_problem`, a
331
+ combined record), because a plain TPTP problem's header is optional,
332
+ decorative metadata. A QMLTP problem's header is NOT decorative — it
333
+ carries the per-logic/per-domain ``Theorem``/``Non-Theorem`` status that
334
+ is this whole format's reason for existing (see this item's own
335
+ test_oracle: reading a problem WITHOUT its status is reading half of
336
+ it), so :func:`parse_qmltp` / :func:`load_qmltp` return this combined
337
+ record directly rather than offering a "formulas only" variant nothing
338
+ would realistically use.
339
+ """
340
+
341
+ formulas: Tuple[QmltpFormula, ...]
342
+ header: QmltpHeader
343
+
344
+
345
+ def parse_qmltp_formula(text: str) -> Node:
346
+ """Parse a single bare QMLTP formula (no ``qmf(...)`` wrapper) into a Node.
347
+
348
+ Args:
349
+ text: a QMLTP formula, e.g. ``"#box : ( ! [X] : ( f(X) ) )"``.
350
+
351
+ Returns:
352
+ The formula as a toolkit :class:`Node` (``#box``/``#dia`` become
353
+ :class:`~fol.nodes.Box` / :class:`~fol.nodes.Diamond`; everything
354
+ else parses exactly as :func:`fol.tptp_input.parse_tptp_formula`
355
+ would).
356
+
357
+ Raises:
358
+ ParsingError: ``text`` is not a well-formed QMLTP formula.
359
+ NotImplementedError: ``text`` uses an indexed ``#box(...)``/
360
+ ``#dia(...)`` connective — see module docstring 'Scope'.
361
+ """
362
+ return _parse(text, _FORMULA_PARSER, "formula")
363
+
364
+
365
+ # A header field line: optional leading whitespace, '%', optional whitespace,
366
+ # one of the four recognised QMLTP field names (matched case-sensitively),
367
+ # optional whitespace, ':', optional whitespace, then the rest of the line as
368
+ # the raw value. Mirrors fol.tptp_input._HEADER_FIELD_RE, with QMLTP's own
369
+ # extra "Source" field and WITHOUT "Status"/"Rating" (QMLTP's Status is a
370
+ # multi-row TABLE, not a single value -- parsed separately below).
371
+ _QMLTP_FIELD_RE = re.compile(r"^\s*%\s*(File|Domain|Problem|Source)\s*:\s*(.*)$")
372
+ # The "% Status : varying cumulative constant" header line --
373
+ # group(1) captures the column (domain-condition) labels, whitespace-joined.
374
+ _QMLTP_STATUS_HEADER_RE = re.compile(r"^\s*%\s*Status\s*:\s*(.*)$")
375
+ # One status TABLE row: "% K Non-Theorem Theorem Theorem".
376
+ # The row label is restricted to the five logic names QMLTP actually uses,
377
+ # so this can never accidentally match the "% Rating :" header line that
378
+ # immediately follows the table (its own row label would be "Rating", which
379
+ # this pattern does not accept) or a Rating-table row (whose FIRST token
380
+ # after the logic-name label is a float, not a Theorem/Non-Theorem word --
381
+ # though the label check alone already prevents any bleed-through, since a
382
+ # rating row starts "% K 0.50 ..." and IS syntactically a candidate match
383
+ # here; the boundary that actually stops the scan is the "% Rating :" HEADER
384
+ # line between the two tables, which this pattern does not match at all --
385
+ # see _parse_qmltp_header's row-consuming loop).
386
+ _QMLTP_STATUS_ROW_RE = re.compile(r"^\s*%\s*(K|D|T|S4|S5)\s+(.*)$")
387
+
388
+
389
+ def _parse_qmltp_header(text: str) -> QmltpHeader:
390
+ """Scan raw QMLTP text for its ``%`` header block, independent of the
391
+ grammar -- the QMLTP counterpart of
392
+ :func:`fol.tptp_input._parse_tptp_header`, with its own regex-based
393
+ parser for the ``% Status`` TABLE (QMLTP's header shape, verified
394
+ directly against the real library, is a multi-row/multi-column table,
395
+ not the single ``% Status : Theorem`` line a plain TPTP problem or the
396
+ roadmap description that named this module would suggest -- see module
397
+ docstring).
398
+
399
+ Every line whose first non-whitespace character is ``%`` is collected
400
+ verbatim (trailing newline stripped) into ``comments``, in source
401
+ order. Among those: a ``File``/``Domain``/``Problem``/``Source`` field
402
+ line populates the matching :class:`QmltpHeader` attribute (first
403
+ occurrence wins); the FIRST ``% Status :`` line starts the status-table
404
+ scan, which reads the header's own column (domain-condition) labels and
405
+ then consumes every immediately-following ``% <logic> <verdict>...``
406
+ row, stopping at the first ``%`` line that is not one (the blank ``%``
407
+ separator that precedes ``% Rating`` in every problem checked, though
408
+ the row pattern's restriction to the five known logic names would stop
409
+ it there regardless -- see :data:`_QMLTP_STATUS_ROW_RE`).
410
+
411
+ Args:
412
+ text: the raw contents of a QMLTP problem file (or any QMLTP text).
413
+
414
+ Returns:
415
+ A :class:`QmltpHeader`; every field is ``None``, ``statuses`` is
416
+ ``()`` and ``comments`` is ``()`` if the text has no ``%`` lines at
417
+ all (or none matching the recognised shapes).
418
+ """
419
+ comments = []
420
+ fields = {}
421
+ statuses = []
422
+ lines = text.splitlines()
423
+ i, n = 0, len(lines)
424
+ status_table_seen = False
425
+ while i < n:
426
+ line = lines[i]
427
+ if not line.lstrip().startswith("%"):
428
+ i += 1
429
+ continue
430
+ comments.append(line)
431
+
432
+ field_match = _QMLTP_FIELD_RE.match(line)
433
+ if field_match:
434
+ field, value = field_match.group(1), field_match.group(2)
435
+ if field not in fields:
436
+ fields[field] = value.strip()
437
+ i += 1
438
+ continue
439
+
440
+ status_header_match = _QMLTP_STATUS_HEADER_RE.match(line)
441
+ if status_header_match and not status_table_seen:
442
+ status_table_seen = True
443
+ domain_names = status_header_match.group(1).split()
444
+ i += 1
445
+ while i < n:
446
+ row_line = lines[i]
447
+ if not row_line.lstrip().startswith("%"):
448
+ break
449
+ row_match = _QMLTP_STATUS_ROW_RE.match(row_line)
450
+ if not row_match:
451
+ break
452
+ comments.append(row_line)
453
+ logic = row_match.group(1)
454
+ tokens = row_match.group(2).split()
455
+ for domain_name, verdict in zip(domain_names, tokens):
456
+ statuses.append(QmltpStatus(logic, domain_name, verdict))
457
+ i += 1
458
+ continue
459
+
460
+ i += 1
461
+
462
+ return QmltpHeader(
463
+ file=fields.get("File"),
464
+ domain=fields.get("Domain"),
465
+ problem=fields.get("Problem"),
466
+ source=fields.get("Source"),
467
+ statuses=tuple(statuses),
468
+ comments=tuple(comments),
469
+ )
470
+
471
+
472
+ # A top-level "tpi(...)" statement's own payload (a bracketed
473
+ # name/system-pair list, e.g. "modal([cumulative,rigid,local],
474
+ # [(fool,s4),(a,s4)])") does not fit this reader's reused fof/tff TERM
475
+ # grammar at all (a "[...]" list or a "(name,system)" pair is not a legal
476
+ # TPTP term) -- so a real multi-modal file fails to parse with a generic,
477
+ # unhelpful "no terminal matches '['" syntax error before _QmltpTransformer
478
+ # .stmt ever runs (that method's own "keyword == 'tpi'" check is kept as a
479
+ # defensive fallback for the rare tpi(...) whose payload WOULD otherwise
480
+ # parse). This cheap pre-scan catches the real construct up front and
481
+ # refuses it BY NAME instead.
482
+ # (?:^|(?<=\r)): a line also starts after a bare CR, which (?m)^ ignores.
483
+ _TPI_STATEMENT_RE = re.compile(r"(?:^|(?<=\r))\s*tpi\s*\(", re.MULTILINE)
484
+
485
+
486
+ def parse_qmltp(text: str) -> QmltpProblem:
487
+ """Parse a whole QMLTP problem into its formulas *and* its header metadata.
488
+
489
+ Args:
490
+ text: the contents of a QMLTP problem file — one or more
491
+ ``qmf(...)`` statements (``%`` line comments are ignored by the
492
+ grammar; the header scan below reads them independently).
493
+
494
+ Returns:
495
+ A :class:`QmltpProblem` with ``formulas`` (in source order) and
496
+ ``header``.
497
+
498
+ Raises:
499
+ ParsingError: ``text`` is not a well-formed QMLTP problem.
500
+ NotImplementedError: ``text`` uses a construct outside this
501
+ reader's supported fragment (a ``tpi(...)`` multi-modal
502
+ directive, or an indexed ``#box(...)``/``#dia(...)``
503
+ connective) — see module docstring 'Scope'.
504
+ """
505
+ if _TPI_STATEMENT_RE.search(text):
506
+ raise NotImplementedError(
507
+ "qmltp_input: 'tpi(...)' multi-modal set_logic directives (the "
508
+ "MML domain) are outside this reader's supported mono-modal "
509
+ "fragment -- see module docstring 'Scope'.")
510
+ items = _parse(text, _FILE_PARSER, "problem")
511
+ return QmltpProblem(formulas=tuple(items), header=_parse_qmltp_header(text))
512
+
513
+
514
+ def load_qmltp(path: str) -> QmltpProblem:
515
+ """Read a QMLTP problem file and :func:`parse_qmltp` its contents.
516
+
517
+ Args:
518
+ path: path to a ``.p`` QMLTP problem file, read as UTF-8.
519
+
520
+ Returns:
521
+ A :class:`QmltpProblem`.
522
+ """
523
+ with open(path, "r", encoding="utf-8") as handle:
524
+ text = handle.read()
525
+ return parse_qmltp(text)