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,570 @@
1
+ """Read APE's DRS term into a faithful 1:1 object model — no interpretation.
2
+
3
+ APE prints its Discourse Representation Structure as one Prolog term::
4
+
5
+ drs([A,B],[object(A,man,countable,na,eq,1)-1/2,predicate(B,wait,A)-1/3])
6
+
7
+ This module parses exactly that surface into frozen dataclasses and does
8
+ NOTHING else: no renaming, no dropping, no semantic choices. Interpretation —
9
+ which conditions the kit can carry, and as what — is
10
+ :mod:`unicode_logic_kit.ace.mapping`'s job, and keeping the two apart is what
11
+ makes the mapping's per-condition coverage report trustworthy: every condition
12
+ APE emitted is HERE, so a condition the report does not mention cannot exist.
13
+
14
+ The shape inventory below is measured, not copied from documentation — every
15
+ form appears in ``tests/fixtures/ape_5f4d535_corpus_v1.json``, recorded from
16
+ APE at the pinned commit over the kit's own 55-sentence corpus:
17
+
18
+ - atomic conditions ``functor(args)-S/T`` with a sentence/token index
19
+ (``-1/2``; implicit conditions carry ``-1/''`` — an EMPTY token). Functors
20
+ seen: ``object/6``, ``predicate/3..5``, ``property/3..4``, ``relation/3``,
21
+ ``modifier_adv/3``, ``modifier_pp/3``, ``has_part/2``, ``query/2``,
22
+ ``formula/3``.
23
+ - complex conditions, none of which carry an index: ``-(drs)`` negation,
24
+ ``~(drs)`` negation as failure, ``=>(drs,drs)`` the duplex condition,
25
+ ``v(drs,drs)`` disjunction, the four modal boxes ``must/can/should/may
26
+ (drs)``, ``question(drs)``, ``command(drs)``.
27
+ - a plain Prolog LIST of conditions as itself a condition — APE's grouping
28
+ for ``exactly``/``at most`` cardinalities (the group carries the maximality
29
+ reading jointly, which is why it is not just conjunction and why
30
+ :mod:`.mapping` refuses it until the Count route exists).
31
+ - terms: variables (``A``), lowercase atoms (``man``, ``eq``, ``na``,
32
+ ``who``, ``comp_than``), integers (bare, as in ``eq,1``), and the wrapped
33
+ values ``named('John')``, ``int(30)``, ``real(...)``, ``string('Johnny')``,
34
+ ``expr(+,int(1),int(2))`` (arithmetic, nesting).
35
+
36
+ The parser is a hand-written tokenizer + recursive descent over THIS
37
+ inventory, generic enough that an unseen functor still parses (into
38
+ :class:`AceAtom` / :class:`AceTermApp`) rather than crashing — it becomes the
39
+ mapping layer's "not in the measured corpus" report row instead. A text that
40
+ is not even a well-formed term (truncated output, a changed APE) raises
41
+ :class:`AceDrsUnreadError` with position and context.
42
+ """
43
+
44
+ from __future__ import annotations
45
+
46
+ import re
47
+ from dataclasses import dataclass
48
+ from typing import List, Optional, Tuple, Union
49
+
50
+ from ..fol._fol_nodes import _numeral_from_text
51
+ from .runner import AceError
52
+
53
+ __all__ = [
54
+ "parse_ape_drs", "AceDrsUnreadError",
55
+ "AceDrs", "AceVar", "AceNamed", "AceInt", "AceReal", "AceString",
56
+ "AceExpr", "AceTermApp",
57
+ "AceAtom", "AceNeg", "AceNaf", "AceImpl", "AceOr", "AceModal",
58
+ "AceQuestion", "AceCommand", "AceCondList",
59
+ ]
60
+
61
+
62
+ class AceDrsUnreadError(AceError):
63
+ """APE's DRS text is not a term this reader understands — either not a
64
+ well-formed Prolog term at all, or a structure outside every shape in
65
+ the measured inventory (see the module docstring)."""
66
+
67
+
68
+ # ---------------------------------------------------------------------------
69
+ # Terms
70
+ # ---------------------------------------------------------------------------
71
+
72
+ @dataclass(frozen=True)
73
+ class AceVar:
74
+ """A discourse referent as APE names it (``A``, ``B``, …). Kept verbatim;
75
+ the kit-side renaming is the mapping layer's concern."""
76
+
77
+ name: str
78
+
79
+ def render(self) -> str:
80
+ return self.name
81
+
82
+
83
+ @dataclass(frozen=True)
84
+ class AceNamed:
85
+ """``named('John')`` — a proper name."""
86
+
87
+ name: str
88
+
89
+ def render(self) -> str:
90
+ return f"named('{self.name}')"
91
+
92
+
93
+ @dataclass(frozen=True)
94
+ class AceInt:
95
+ value: int
96
+
97
+ def render(self) -> str:
98
+ return f"int({self.value})"
99
+
100
+
101
+ @dataclass(frozen=True)
102
+ class AceReal:
103
+ """``real(3.14)`` — a decimal value, read exactly or refused.
104
+
105
+ A ``float`` when a float holds the numeral exactly, the ``int`` itself when only an integer
106
+ does (``real(100000000000000000000000.0)`` is ``10**23``, which no float is). A decimal of
107
+ more than 15 significant digits is refused by the reader with :class:`AceDrsUnreadError`: two
108
+ different decimals of that length can be one float, and a numeral is identified by its value.
109
+ """
110
+
111
+ value: Union[int, float]
112
+
113
+ def render(self) -> str:
114
+ return f"real({self.value})"
115
+
116
+
117
+ @dataclass(frozen=True)
118
+ class AceString:
119
+ value: str
120
+
121
+ def render(self) -> str:
122
+ return f"string('{self.value}')"
123
+
124
+
125
+ @dataclass(frozen=True)
126
+ class AceExpr:
127
+ """``expr(+, int(1), int(2))`` — an arithmetic expression term."""
128
+
129
+ op: str
130
+ left: "AceTerm"
131
+ right: "AceTerm"
132
+
133
+ def render(self) -> str:
134
+ return f"expr({self.op},{_render(self.left)},{_render(self.right)})"
135
+
136
+
137
+ @dataclass(frozen=True)
138
+ class AceTermApp:
139
+ """A functored term outside the known wrappers — kept verbatim so an APE
140
+ novelty surfaces in the coverage report instead of crashing the reader."""
141
+
142
+ functor: str
143
+ args: Tuple["AceTerm", ...]
144
+
145
+ def render(self) -> str:
146
+ return f"{self.functor}({','.join(_render(a) for a in self.args)})"
147
+
148
+
149
+ #: A term: a referent, a bare atom (str), or one of the wrapped values.
150
+ AceTerm = Union[AceVar, str, int, AceNamed, AceInt, AceReal, AceString,
151
+ AceExpr, AceTermApp]
152
+
153
+
154
+ def _render(term: AceTerm) -> str:
155
+ if isinstance(term, str):
156
+ return term
157
+ if isinstance(term, int):
158
+ return str(term)
159
+ return term.render()
160
+
161
+
162
+ # ---------------------------------------------------------------------------
163
+ # Conditions
164
+ # ---------------------------------------------------------------------------
165
+
166
+ @dataclass(frozen=True)
167
+ class AceAtom:
168
+ """An atomic condition: ``functor(args)`` plus its sentence/token index.
169
+
170
+ ``token`` is ``None`` for implicit conditions (APE prints ``-1/''`` for
171
+ e.g. the group-membership ``has_part`` facts a coordination introduces).
172
+ """
173
+
174
+ functor: str
175
+ args: Tuple[AceTerm, ...]
176
+ sentence: Optional[int] = None
177
+ token: Optional[int] = None
178
+
179
+ def render(self) -> str:
180
+ body = f"{self.functor}({','.join(_render(a) for a in self.args)})"
181
+ if self.sentence is None:
182
+ return body
183
+ tok = self.token if self.token is not None else "''"
184
+ return f"{body}-{self.sentence}/{tok}"
185
+
186
+
187
+ @dataclass(frozen=True)
188
+ class AceNeg:
189
+ """``-(drs)`` — classical negation of a sub-box."""
190
+
191
+ drs: "AceDrs"
192
+
193
+ def render(self) -> str:
194
+ return f"-({self.drs.render()})"
195
+
196
+
197
+ @dataclass(frozen=True)
198
+ class AceNaf:
199
+ """``~(drs)`` — negation as failure ("it is not provable that")."""
200
+
201
+ drs: "AceDrs"
202
+
203
+ def render(self) -> str:
204
+ return f"~({self.drs.render()})"
205
+
206
+
207
+ @dataclass(frozen=True)
208
+ class AceImpl:
209
+ """``=>(drs,drs)`` — the duplex condition (every/no/if-then/each-of)."""
210
+
211
+ antecedent: "AceDrs"
212
+ consequent: "AceDrs"
213
+
214
+ def render(self) -> str:
215
+ return f"=>({self.antecedent.render()},{self.consequent.render()})"
216
+
217
+
218
+ @dataclass(frozen=True)
219
+ class AceOr:
220
+ """``v(drs,drs)`` — disjunction of two sub-boxes."""
221
+
222
+ left: "AceDrs"
223
+ right: "AceDrs"
224
+
225
+ def render(self) -> str:
226
+ return f"v({self.left.render()},{self.right.render()})"
227
+
228
+
229
+ @dataclass(frozen=True)
230
+ class AceModal:
231
+ """``must/can/should/may (drs)`` — one of ACE's four modal boxes."""
232
+
233
+ modality: str
234
+ drs: "AceDrs"
235
+
236
+ def render(self) -> str:
237
+ return f"{self.modality}({self.drs.render()})"
238
+
239
+
240
+ @dataclass(frozen=True)
241
+ class AceQuestion:
242
+ """``question(drs)`` — an interrogative sentence's box."""
243
+
244
+ drs: "AceDrs"
245
+
246
+ def render(self) -> str:
247
+ return f"question({self.drs.render()})"
248
+
249
+
250
+ @dataclass(frozen=True)
251
+ class AceCommand:
252
+ """``command(drs)`` — an imperative sentence's box."""
253
+
254
+ drs: "AceDrs"
255
+
256
+ def render(self) -> str:
257
+ return f"command({self.drs.render()})"
258
+
259
+
260
+ @dataclass(frozen=True)
261
+ class AceCondList:
262
+ """A LIST of conditions as itself a condition — APE's joint grouping for
263
+ ``exactly``/``at most`` cardinalities (see the module docstring)."""
264
+
265
+ conditions: Tuple["AceCondition", ...]
266
+
267
+ def render(self) -> str:
268
+ return "[" + ",".join(c.render() for c in self.conditions) + "]"
269
+
270
+
271
+ AceCondition = Union[AceAtom, AceNeg, AceNaf, AceImpl, AceOr, AceModal,
272
+ AceQuestion, AceCommand, AceCondList]
273
+
274
+ #: Complex-condition functors, mapped to their dataclass. ``v`` is a plain
275
+ #: atom name in Prolog, so classification happens AFTER a generic parse: an
276
+ #: application is complex iff its functor is listed here AND every argument
277
+ #: is a DRS — ``v(A,B)`` with referent args would stay an ordinary AceAtom.
278
+ _COMPLEX = {
279
+ "-": (AceNeg, 1), "~": (AceNaf, 1), "=>": (AceImpl, 2), "v": (AceOr, 2),
280
+ "question": (AceQuestion, 1), "command": (AceCommand, 1),
281
+ "must": (AceModal, 1), "can": (AceModal, 1), "should": (AceModal, 1),
282
+ "may": (AceModal, 1),
283
+ }
284
+
285
+
286
+ @dataclass(frozen=True)
287
+ class AceDrs:
288
+ """One box: declared referents plus conditions, verbatim from APE."""
289
+
290
+ referents: Tuple[AceVar, ...]
291
+ conditions: Tuple[AceCondition, ...]
292
+
293
+ def render(self) -> str:
294
+ refs = ",".join(v.name for v in self.referents)
295
+ conds = ",".join(c.render() for c in self.conditions)
296
+ return f"drs([{refs}],[{conds}])"
297
+
298
+ def walk_conditions(self):
299
+ """Every condition in the tree, pre-order — sub-boxes AND the inner
300
+ conditions of a list condition included (a list's members are
301
+ conditions in their own right; skipping them made the renamer's
302
+ event detection blind to ``exactly``/``at most`` scopes)."""
303
+ for cond in self.conditions:
304
+ yield from _walk_condition(cond)
305
+
306
+ def walk_boxes(self):
307
+ """Every box in the tree, pre-order, this one first."""
308
+ yield self
309
+ for cond in self.conditions:
310
+ for sub in _sub_boxes(cond):
311
+ yield from sub.walk_boxes()
312
+
313
+
314
+ def _walk_condition(cond: "AceCondition"):
315
+ yield cond
316
+ if isinstance(cond, AceCondList):
317
+ for inner in cond.conditions:
318
+ yield from _walk_condition(inner)
319
+ else:
320
+ for sub in _sub_boxes(cond):
321
+ yield from sub.walk_conditions()
322
+
323
+
324
+ def _sub_boxes(cond: AceCondition):
325
+ if isinstance(cond, (AceNeg, AceNaf, AceModal, AceQuestion, AceCommand)):
326
+ yield cond.drs
327
+ elif isinstance(cond, AceImpl):
328
+ yield cond.antecedent
329
+ yield cond.consequent
330
+ elif isinstance(cond, AceOr):
331
+ yield cond.left
332
+ yield cond.right
333
+ elif isinstance(cond, AceCondList):
334
+ for inner in cond.conditions:
335
+ yield from _sub_boxes(inner)
336
+
337
+
338
+ # ---------------------------------------------------------------------------
339
+ # Tokenizer + recursive descent
340
+ # ---------------------------------------------------------------------------
341
+
342
+ _TOKEN = re.compile(r"""
343
+ (?P<ws>\s+)
344
+ | (?P<real>\d+\.\d+)
345
+ | (?P<int>\d+)
346
+ | (?P<var>[A-Z_][A-Za-z0-9_]*)
347
+ | (?P<atom>[a-z][A-Za-z0-9_]*)
348
+ | (?P<quoted>'(?:[^']|'')*')
349
+ | (?P<symbol>=>|\\/|[-~=+*/^<>])
350
+ | (?P<punct>[()\[\],|])
351
+ """, re.VERBOSE)
352
+
353
+
354
+ def _tokenize(text: str) -> List[Tuple[str, str, int]]:
355
+ tokens = []
356
+ pos = 0
357
+ while pos < len(text):
358
+ match = _TOKEN.match(text, pos)
359
+ if match is None:
360
+ raise AceDrsUnreadError(
361
+ f"DRS reader: unexpected character {text[pos]!r} at offset "
362
+ f"{pos} in {text[max(0, pos - 30):pos + 30]!r}")
363
+ kind = match.lastgroup
364
+ if kind != "ws":
365
+ tokens.append((kind, match.group(), pos))
366
+ pos = match.end()
367
+ tokens.append(("end", "", pos))
368
+ return tokens
369
+
370
+
371
+ class _Parser:
372
+ def __init__(self, text: str):
373
+ self.text = text
374
+ self.tokens = _tokenize(text)
375
+ self.i = 0
376
+
377
+ # -- token plumbing ----------------------------------------------------
378
+ def peek(self, ahead: int = 0) -> Tuple[str, str]:
379
+ kind, value, _ = self.tokens[min(self.i + ahead, len(self.tokens) - 1)]
380
+ return kind, value
381
+
382
+ def take(self, kind: Optional[str] = None, value: Optional[str] = None):
383
+ actual_kind, actual_value, pos = self.tokens[self.i]
384
+ if ((kind is not None and actual_kind != kind)
385
+ or (value is not None and actual_value != value)):
386
+ want = value if value is not None else kind
387
+ raise AceDrsUnreadError(
388
+ f"DRS reader: expected {want!r}, found {actual_value!r} at "
389
+ f"offset {pos} in {self.text[max(0, pos - 30):pos + 30]!r}")
390
+ self.i += 1
391
+ return actual_value
392
+
393
+ # -- grammar -----------------------------------------------------------
394
+ def parse(self) -> AceDrs:
395
+ drs = self.drs()
396
+ self.take("end")
397
+ return drs
398
+
399
+ def drs(self) -> AceDrs:
400
+ self.take("atom", "drs")
401
+ self.take("punct", "(")
402
+ self.take("punct", "[")
403
+ referents: List[AceVar] = []
404
+ while self.peek() != ("punct", "]"):
405
+ referents.append(AceVar(self.take("var")))
406
+ if self.peek() == ("punct", ","):
407
+ self.take()
408
+ self.take("punct", "]")
409
+ self.take("punct", ",")
410
+ conditions = self.condition_list()
411
+ self.take("punct", ")")
412
+ return AceDrs(tuple(referents), tuple(conditions))
413
+
414
+ def condition_list(self) -> List[AceCondition]:
415
+ self.take("punct", "[")
416
+ conditions: List[AceCondition] = []
417
+ while self.peek() != ("punct", "]"):
418
+ conditions.append(self.condition())
419
+ if self.peek() == ("punct", ","):
420
+ self.take()
421
+ self.take("punct", "]")
422
+ return conditions
423
+
424
+ def condition(self) -> AceCondition:
425
+ kind, value = self.peek()
426
+ if (kind, value) == ("punct", "["):
427
+ return AceCondList(tuple(self.condition_list()))
428
+ if kind in ("atom", "symbol") and self.peek(1) == ("punct", "("):
429
+ functor = self.take()
430
+ args = self.application_args()
431
+ complex_entry = _COMPLEX.get(functor)
432
+ if (complex_entry is not None
433
+ and len(args) == complex_entry[1]
434
+ and all(isinstance(a, AceDrs) for a in args)):
435
+ cls = complex_entry[0]
436
+ if cls is AceModal:
437
+ return AceModal(functor, *args)
438
+ return cls(*args)
439
+ atom_args = tuple(a for a in args)
440
+ sentence, token = self.optional_index()
441
+ return AceAtom(functor, atom_args, sentence, token)
442
+ raise AceDrsUnreadError(
443
+ f"DRS reader: a condition cannot start with {value!r} "
444
+ f"(offset {self.tokens[self.i][2]})")
445
+
446
+ def application_args(self) -> List:
447
+ """The argument list of ``functor(...)`` — each argument is a term,
448
+ or a whole sub-``drs`` for the complex conditions."""
449
+ self.take("punct", "(")
450
+ args: List = []
451
+ while self.peek() != ("punct", ")"):
452
+ if self.peek() == ("atom", "drs") and self.peek(1) == ("punct", "("):
453
+ args.append(self.drs())
454
+ else:
455
+ args.append(self.term())
456
+ if self.peek() == ("punct", ","):
457
+ self.take()
458
+ self.take("punct", ")")
459
+ return args
460
+
461
+ def optional_index(self) -> Tuple[Optional[int], Optional[int]]:
462
+ """``-S/T`` after an atomic condition; ``T`` may be ``''``."""
463
+ if self.peek() != ("symbol", "-"):
464
+ return None, None
465
+ kind, _ = self.peek(1)
466
+ if kind != "int":
467
+ return None, None
468
+ self.take("symbol", "-")
469
+ sentence = int(self.take("int"))
470
+ self.take("symbol", "/")
471
+ kind, _ = self.peek()
472
+ if kind == "int":
473
+ return sentence, int(self.take("int"))
474
+ quoted = self.take("quoted")
475
+ if quoted != "''":
476
+ raise AceDrsUnreadError(
477
+ f"DRS reader: expected an integer or '' as token index, "
478
+ f"found {quoted!r}")
479
+ return sentence, None
480
+
481
+ def real(self, text: str) -> Union[int, float]:
482
+ """The value of the decimal ``text``, read exactly or refused by name.
483
+
484
+ The rule of every numeral reader of the kit: a whole value is an ``int`` of its digits, any
485
+ other decimal is the float it spells when it has at most 15 significant digits, and a
486
+ longer one is refused, because two different decimals of that length can be one float and
487
+ a numeral is identified by its value.
488
+
489
+ Raises:
490
+ AceDrsUnreadError: the text is a decimal that no float holds exactly.
491
+ """
492
+ try:
493
+ return _numeral_from_text(text)
494
+ except ValueError as exc:
495
+ raise AceDrsUnreadError(f"DRS reader: the real {text} is not read: {exc}") from None
496
+
497
+ def term(self) -> AceTerm:
498
+ kind, value = self.peek()
499
+ if kind == "var":
500
+ self.take()
501
+ return AceVar(value)
502
+ if kind == "int":
503
+ self.take()
504
+ return int(value)
505
+ if kind == "real":
506
+ self.take()
507
+ return self.real(value)
508
+ if kind == "quoted":
509
+ self.take()
510
+ return _unquote(value)
511
+ if kind == "symbol":
512
+ # `-` directly before a number is a sign; any symbol is
513
+ # otherwise a bare symbolic atom (the operator argument of
514
+ # ``expr(+,...)`` / ``formula(...,=,...)``).
515
+ if value == "-" and self.peek(1)[0] in ("int", "real"):
516
+ self.take()
517
+ num_kind, num_value = self.peek()
518
+ self.take()
519
+ return (-int(num_value) if num_kind == "int"
520
+ else -self.real(num_value))
521
+ self.take()
522
+ return value
523
+ if kind == "atom":
524
+ self.take()
525
+ if self.peek() == ("punct", "("):
526
+ args = self.application_args()
527
+ return _wrap_term(value, args, self.text)
528
+ return value
529
+ raise AceDrsUnreadError(
530
+ f"DRS reader: a term cannot start with {value!r} "
531
+ f"(offset {self.tokens[self.i][2]})")
532
+
533
+
534
+ def _unquote(quoted: str) -> str:
535
+ return quoted[1:-1].replace("''", "'")
536
+
537
+
538
+ def _real_value(number: Union[int, float]) -> Union[int, float]:
539
+ """``number`` as the float that holds it exactly, else as the ``int`` itself (never a float that is another number)."""
540
+ if isinstance(number, float):
541
+ return number
542
+ try:
543
+ as_float = float(number)
544
+ except OverflowError:
545
+ return number
546
+ return as_float if as_float == number else number
547
+
548
+
549
+ def _wrap_term(functor: str, args: List, text: str) -> AceTerm:
550
+ """Specialize the wrapped-value terms; keep everything else verbatim."""
551
+ if functor == "named" and len(args) == 1 and isinstance(args[0], str):
552
+ return AceNamed(args[0])
553
+ if functor == "int" and len(args) == 1 and isinstance(args[0], int):
554
+ return AceInt(args[0])
555
+ if functor == "real" and len(args) == 1 and isinstance(args[0], (int, float)):
556
+ return AceReal(_real_value(args[0]))
557
+ if functor == "string" and len(args) == 1 and isinstance(args[0], str):
558
+ return AceString(args[0])
559
+ if functor == "expr" and len(args) == 3 and isinstance(args[0], str):
560
+ return AceExpr(args[0], args[1], args[2])
561
+ return AceTermApp(functor, tuple(args))
562
+
563
+
564
+ def parse_ape_drs(text: str) -> AceDrs:
565
+ """APE's printed DRS term → the 1:1 object model (see module docstring).
566
+
567
+ Raises :class:`AceDrsUnreadError` with offset and context on anything
568
+ that is not a well-formed term of the measured inventory.
569
+ """
570
+ return _Parser(text.strip()).parse()