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,323 @@
1
+ """Shared machinery for the two third-order HOL exporters.
2
+
3
+ :mod:`unicode_logic_kit.hol.thirdorder` (classical) and
4
+ :mod:`unicode_logic_kit.hol.ho_modal` (the shallow modal embedding) differ in
5
+ exactly one thing: whether a proposition is a truth value or a function from
6
+ worlds to truth values. Everything BEFORE that decision is the same work —
7
+ renaming bound predicate variables apart so a theory-wide signature analysis
8
+ answers the right question, collecting the free symbols that need declaring, and
9
+ peeling λ-binders off a property argument — so it lives here rather than twice.
10
+ """
11
+
12
+ import re
13
+ from typing import Dict, Iterable, List, Sequence, Set, Tuple
14
+
15
+ from ..fol.nodes import (
16
+ Node, Atom, Quantifier, SecondOrderQuantifier, PredicateTerm,
17
+ Variable, Constant, Function, LambdaVar, Lambda,
18
+ )
19
+ from ..fol._symbol_names import dedupe
20
+ from ..fol._truth_constants import truth_value
21
+ from ..fol.qml import _thf_name
22
+
23
+ #: The two kinds of individual symbol a writer declares. A free variable is a PARAMETER of the
24
+ #: problem: one unknown element, the same in every formula (see
25
+ #: :mod:`unicode_logic_kit.fol._free_parameters`). A constant is a particular element that its
26
+ #: name stands for. ``P(x)`` with a free ``x`` and ``P('x')`` are two statements, so the two are
27
+ #: two symbols of the output even when they are spelled alike. The string ``"individual"`` is the
28
+ #: key the free variable has always had.
29
+ FREE_VARIABLE = "individual"
30
+ CONSTANT = "constant"
31
+
32
+ _LOWER_WORD = re.compile(r"[a-z][A-Za-z0-9_]*\Z")
33
+
34
+
35
+ def functor_stem(name: str) -> str:
36
+ """A TPTP lower word (``[a-z][A-Za-z0-9_]*``) that ``name`` is turned into; NOT injective.
37
+
38
+ This is the kit's THF stem (:func:`~unicode_logic_kit.fol.qml._thf_name`: ASCII, a Greek
39
+ letter by its name, any other non-ASCII character as a ``uXXXX`` escape, every other
40
+ character as ``_``, a ``p`` in front of a leading digit, a lower-case initial) with one
41
+ repair: a name that starts with an underscore (``_sk0``, ``-3``) would still not be a lower
42
+ word, so it gets a ``p`` in front as well. Two names can have one stem (``a b`` and ``a_b``);
43
+ :func:`~unicode_logic_kit.fol._symbol_names.dedupe` over one pool is what keeps the symbols
44
+ apart.
45
+ """
46
+ stem = _thf_name(name)
47
+ return stem if _LOWER_WORD.match(stem) else "p" + stem
48
+
49
+
50
+ class UnsupportedHigherOrderNode(NotImplementedError):
51
+ """Raised for an AST node a third-order embedding has no reading for.
52
+
53
+ Subclasses NotImplementedError, matching the kit's other export refusals: a
54
+ route that cannot carry a construct says so by name instead of dropping it.
55
+ """
56
+
57
+
58
+ #: The ordering predicates ``<`` ``>`` ``≤`` ``≥``. Neither target format has an
59
+ #: ordering on the uninterpreted individual type, so they are ordinary
60
+ #: uninterpreted relations (WORLD-relativised in the modal embedding, exactly as
61
+ #: :mod:`unicode_logic_kit.fol.qml` reads them) under the stems named here.
62
+ ORDERING = {"<": "flt", ">": "fgt", "≤": "fle", "≥": "fge"}
63
+
64
+ #: The aliases of the CLASSICAL third-order exporter
65
+ #: (:mod:`unicode_logic_kit.hol.thirdorder`), which reads ``=`` / ``≠`` as the
66
+ #: uninterpreted relations ``feq`` / ``fneq`` — not primitive HOL identity — next to
67
+ #: the :data:`ORDERING` predicates. The MODAL exporter
68
+ #: (:mod:`unicode_logic_kit.hol.ho_modal`) does NOT use the first two entries: there
69
+ #: object identity is RIGID (HOL's own ``=`` over the individual type, no world
70
+ #: argument, as in :func:`unicode_logic_kit.fol.qml.qml_is_valid`), so it takes only
71
+ #: :data:`ORDERING` and reads ``=`` / ``≠`` through
72
+ #: :func:`unicode_logic_kit.hol.isabelle_modal._lower_identity`.
73
+ EQUALITY = {"=": "feq", "≠": "fneq", **ORDERING}
74
+
75
+
76
+ def peel_lambdas(node: Node) -> Tuple[List[str], Node]:
77
+ """Split a (possibly nested) Lambda into its binder names and its body.
78
+
79
+ ``λx. λy. φ`` denotes a binary relation, so the number of binders IS the
80
+ arity of the property the argument denotes.
81
+ """
82
+ names = []
83
+ while isinstance(node, Lambda):
84
+ names.append(node.param.name)
85
+ node = node.body
86
+ return names, node
87
+
88
+
89
+ def rename_bound_pred(node: Node, old: str, new: str) -> Node:
90
+ """Rename every occurrence of the predicate variable ``old`` to ``new``.
91
+
92
+ Renames it in HEAD position (``Atom``) and in ARGUMENT position
93
+ (``PredicateTerm``) alike, and stops at an inner binder over the same name,
94
+ whose occurrences belong to that binding rather than this one.
95
+ """
96
+ if isinstance(node, SecondOrderQuantifier) and node.predicate == old:
97
+ return node
98
+ if isinstance(node, Atom) and node.predicate == old:
99
+ return Atom(new, [rename_bound_pred(a, old, new) for a in node.args])
100
+ if isinstance(node, PredicateTerm) and node.name == old:
101
+ return PredicateTerm(new)
102
+ return node.map_children(lambda child: rename_bound_pred(child, old, new))
103
+
104
+
105
+ def rename_apart(formulas: Sequence[Node]) -> Tuple[List[Node], Dict[str, str]]:
106
+ """Give every second-order binder a name unique across ``formulas``.
107
+
108
+ A theory-wide signature analysis merges by NAME, and a bound variable's name
109
+ is scoped to its own formula: two axioms may each bind ``P`` at a different
110
+ arity without disagreeing about anything, and a binder may even shadow
111
+ another of the same name inside one formula. Renaming apart first is what
112
+ keeps the analysis answering the question that was actually asked — what the
113
+ FREE symbols are, and what arity each binding really has — instead of a
114
+ false conflict between two local bindings.
115
+
116
+ Returns the renamed formulas and a ``{fresh name: original name}`` map. The
117
+ renaming is for the ANALYSIS; emission renders the renamed tree but prints
118
+ the original names back through that map, which is sound because the emitted
119
+ binders reproduce the original scoping exactly.
120
+ """
121
+ counter = [0]
122
+ original: Dict[str, str] = {}
123
+
124
+ def walk(node: Node) -> Node:
125
+ if isinstance(node, SecondOrderQuantifier):
126
+ counter[0] += 1
127
+ fresh = f"Bound{counter[0]}"
128
+ original[fresh] = node.predicate
129
+ renamed = rename_bound_pred(node.formula, node.predicate, fresh)
130
+ return SecondOrderQuantifier(node.type, fresh, node.arity, walk(renamed))
131
+ return node.map_children(walk)
132
+
133
+ return [walk(f) for f in formulas], original
134
+
135
+
136
+ def bound_pred_names(node: Node, acc: Set[str] = None) -> Set[str]:
137
+ """Collect every predicate-variable name bound by a second-order quantifier."""
138
+ acc = set() if acc is None else acc
139
+ if isinstance(node, SecondOrderQuantifier):
140
+ acc.add(node.predicate)
141
+ for child in node._child_nodes():
142
+ bound_pred_names(child, acc)
143
+ return acc
144
+
145
+
146
+ def atom_predicates(node: Node, acc: Set[str] = None) -> Set[str]:
147
+ """Collect every predicate name applied in ``node``."""
148
+ acc = set() if acc is None else acc
149
+ if isinstance(node, Atom) and truth_value(node) is None:
150
+ acc.add(node.predicate) # (`$true` / `$false` are constants, not predicates)
151
+ for child in node._child_nodes():
152
+ atom_predicates(child, acc)
153
+ return acc
154
+
155
+
156
+ def function_symbols(formulas: Sequence[Node], acc: Dict[str, int] = None) -> Dict[str, int]:
157
+ """Collect ``{function name: arity}`` over ``formulas``."""
158
+ acc = {} if acc is None else acc
159
+ for node in formulas:
160
+ if isinstance(node, Function):
161
+ acc[node.name] = len(node.args)
162
+ function_symbols(list(node._child_nodes()), acc)
163
+ return acc
164
+
165
+
166
+ def constant_name(node: Constant) -> str:
167
+ """The name of the constant ``node``, which has to be a string.
168
+
169
+ Every other kind of name has no identifier in either target, so it is refused here, by
170
+ name, before any text is written.
171
+ """
172
+ if not isinstance(node.name, str):
173
+ raise TypeError(
174
+ f"the name of a constant must be a string, got {node.name!r} "
175
+ f"({type(node.name).__name__}): a constant of that name has no identifier in THF "
176
+ "or in Isabelle; write the name as a string")
177
+ return node.name
178
+
179
+
180
+ def individual_symbols(formulas: Sequence[Node]) -> List[Tuple[str, str]]:
181
+ """The individual symbols of ``formulas`` that a closed theory has to declare, as ``(kind, name)``.
182
+
183
+ ``kind`` is :data:`CONSTANT` for a :class:`~unicode_logic_kit.fol.nodes.Constant` and
184
+ :data:`FREE_VARIABLE` for an object variable bound by no quantifier or λ. A closed HOL theory
185
+ has no free variables, so the second is declared as an individual constant too, but it stays
186
+ the symbol it is: one unknown element, never the constant that is spelled like it.
187
+
188
+ The list is sorted by name and, within one name, the free variable comes first; for a formula
189
+ in which no variable is spelled like a constant that is the order of the sorted names.
190
+ """
191
+ variables: Set[str] = set()
192
+ constants: Set[str] = set()
193
+
194
+ def walk(node: Node, bound: frozenset) -> None:
195
+ if isinstance(node, Constant):
196
+ constants.add(constant_name(node))
197
+ return
198
+ if isinstance(node, Variable):
199
+ if node.name not in bound:
200
+ variables.add(node.name)
201
+ return
202
+ if isinstance(node, LambdaVar):
203
+ return
204
+ if isinstance(node, Quantifier):
205
+ walk(node.formula, bound | {node.variable.name})
206
+ return
207
+ if isinstance(node, Lambda):
208
+ walk(node.body, bound | {node.param.name})
209
+ return
210
+ for child in node._child_nodes():
211
+ walk(child, bound)
212
+
213
+ for formula in formulas:
214
+ walk(formula, frozenset())
215
+ symbols = [(FREE_VARIABLE, name) for name in variables] + [(CONSTANT, name) for name in constants]
216
+ return sorted(symbols, key=lambda symbol: (symbol[1], symbol[0] != FREE_VARIABLE))
217
+
218
+
219
+ class ThfNames:
220
+ """THF identifiers for one problem emitted by a third-order exporter.
221
+
222
+ A THF constant and an annotated formula's name must be a lower word: an
223
+ upper-case initial makes a token a VARIABLE, so the kit's ``Positive``/``G``
224
+ written out verbatim is not a constant at all and the problem does not parse.
225
+ Free symbols therefore go through the kit's THF stem (ASCII, lower-case
226
+ initial, see :func:`functor_stem`) and are made unique — keyed by
227
+ ``(kind, name)``, because THF has one namespace for predicates, individuals
228
+ and functions, and pushed off ``reserved`` (the embedding's own functors) so
229
+ ``G`` and ``g``, or a user predicate named ``r``, cannot collide with each
230
+ other or with the accessibility relation. The kinds a writer uses are
231
+ ``"predicate"``, ``"function"``, ``"nominal"`` and the two of an individual,
232
+ :data:`FREE_VARIABLE` and :data:`CONSTANT`: a free variable and a constant that
233
+ are spelled alike are two symbols with two functors. A bound variable is not
234
+ named here; its token is upper-case (see :func:`bound_token`) and a functor
235
+ lower-case, so a bound variable never has the token of a symbol of this class.
236
+ """
237
+
238
+ def __init__(self, reserved: Sequence[str] = ()):
239
+ self._used: Set[str] = set(reserved)
240
+ self._functors: Dict[Tuple[str, str], str] = {}
241
+ self._units: Set[str] = set()
242
+
243
+ def functor(self, kind: str, name: str) -> str:
244
+ """The unique THF constant for the free symbol ``name`` of ``kind``."""
245
+ key = (kind, name)
246
+ if key not in self._functors:
247
+ self._functors[key] = dedupe(functor_stem(name), self._used)
248
+ return self._functors[key]
249
+
250
+ def unit(self, name: str) -> str:
251
+ """Claim a unique annotated-formula name derived from ``name``."""
252
+ return dedupe(functor_stem(name), self._units)
253
+
254
+
255
+ _ISABELLE_IDENTIFIER = re.compile(r"[A-Za-z][A-Za-z0-9_']*\Z")
256
+
257
+ #: The constants of HOL that the text of an exporter names itself (``True`` and ``False`` for
258
+ #: ``$true`` / ``$false``): a symbol of the formula never takes one of these names.
259
+ ISABELLE_BUILT_IN = frozenset({"True", "False"})
260
+
261
+
262
+ def is_isabelle_identifier(name: str) -> bool:
263
+ """Whether ``name`` is an Isabelle identifier as it stands: an ASCII letter, then ASCII
264
+ letters, digits, underscores and apostrophes."""
265
+ return _ISABELLE_IDENTIFIER.match(name) is not None
266
+
267
+
268
+ class IsabelleNames:
269
+ """Isabelle identifiers for the free symbols of one theory emitted by a third-order exporter.
270
+
271
+ A symbol is keyed by ``(kind, name)`` (``"predicate"``, ``"function"``, :data:`FREE_VARIABLE`,
272
+ :data:`CONSTANT`), and the identifiers are made unique over ONE pool, because an Isabelle
273
+ theory has one namespace for the constants it declares: a constant ``P`` and a predicate
274
+ ``P``, a free variable ``x`` and a constant ``x``, are four symbols and must not be declared
275
+ under one name. ``reserved`` are the names the text already gives a meaning to (the
276
+ embedding's own vocabulary, HOL's ``True`` and ``False``): no symbol takes one of them.
277
+
278
+ A name that is an Isabelle identifier (:func:`is_isabelle_identifier`) is written as it is,
279
+ so ``socrates`` and ``Alice`` keep their names and the text of a formula whose symbols are
280
+ plain words is the text it always was. Any other name (``John Doe``, ``G-910``, ``C++``,
281
+ ``2008SummerOlympics``, ``θ``, ``_sk0``) is written under its :func:`functor_stem`. Names
282
+ that are identifiers claim their own spelling FIRST, so a name that merely sanitises onto
283
+ one (``a b`` next to ``a_b``) is the one pushed to ``a_b_2``.
284
+ """
285
+
286
+ def __init__(self, reserved: Iterable[str] = ()):
287
+ self._used: Set[str] = set(reserved)
288
+ self._tokens: Dict[Tuple[str, str], str] = {}
289
+
290
+ def claim(self, symbols: Iterable[Tuple[str, str]]) -> None:
291
+ """Name ``symbols`` (``(kind, name)`` pairs): those that are identifiers first, each
292
+ group in the order given, so the order a caller gives decides who keeps a contested name."""
293
+ pending = [s for s in dict.fromkeys(symbols) if s not in self._tokens]
294
+ for verbatim in (True, False):
295
+ for kind, name in pending:
296
+ if is_isabelle_identifier(name) == verbatim:
297
+ base = name if verbatim else functor_stem(name)
298
+ self._tokens[(kind, name)] = dedupe(base, self._used)
299
+
300
+ def symbol(self, kind: str, name: str) -> str:
301
+ """The identifier of the symbol ``name`` of ``kind`` (named now when it has none yet)."""
302
+ if (kind, name) not in self._tokens:
303
+ self.claim([(kind, name)])
304
+ return self._tokens[(kind, name)]
305
+
306
+
307
+ def bound_token(name: str, suffix: str, scope: Dict[str, str]) -> str:
308
+ """An upper-case THF variable token for a binder of ``name``.
309
+
310
+ ``scope`` maps each source name bound further out to its token. The result
311
+ differs from every one of those tokens: ``x`` and ``X`` share a stem, and
312
+ reusing it for an inner binder of a DIFFERENT name would capture the outer
313
+ variable's occurrences inside the body.
314
+ """
315
+ stem = _thf_name(name).upper()
316
+ if not stem[:1].isalpha():
317
+ stem = "V" + stem
318
+ stem += suffix
319
+ taken = set(scope.values())
320
+ token, i = stem, 2
321
+ while token in taken:
322
+ token, i = f"{stem}{i}", i + 1
323
+ return token
@@ -0,0 +1,125 @@
1
+ """The names an Isabelle writer gives its binders, kept off every symbol of the theory.
2
+
3
+ An Isabelle binder ``\\<forall>x::i.`` or ``\\<lambda>x::i.`` shadows a constant ``x`` inside its own
4
+ scope, so a binder that is spelled like a constant, a function, a predicate or one of the embedding's
5
+ own abbreviations captures it: ``∀x P(x, c_x)`` written as ``\\<forall>x::i. (P x x)`` is the formula
6
+ ``∀x P(x, x)``. The writers of the second order, the third order and the third-order modal embedding
7
+ print the names of the source, so each of them asks this module for the name of every binder: the
8
+ source name when no symbol of the theory is spelled like it, a suffixed variant (``x_2``) when one is.
9
+
10
+ A binder is either a VARIABLE (an object quantifier, a lambda parameter, the variable of a
11
+ cardinality term: all individuals, one namespace) or a PREDICATE variable (bound by a second-order
12
+ quantifier). Two binders of one kind and one name are one token: shadowing means the same in the
13
+ text as in the source. A variable and a predicate variable of one name get two tokens.
14
+ """
15
+
16
+ import re
17
+ from typing import Dict, FrozenSet, Iterable, List, Mapping, Optional, Tuple
18
+
19
+ from ..fol._symbol_names import dedupe
20
+ from ..fol.nodes import (
21
+ Node, Quantifier, Cardinality, SortedCardinality, Lambda, SecondOrderQuantifier,
22
+ )
23
+
24
+ __all__ = ["VARIABLE", "PREDICATE", "declared_names", "binder_tokens", "collect_binders",
25
+ "BinderScope"]
26
+
27
+ VARIABLE = "variable"
28
+ PREDICATE = "predicate"
29
+
30
+ _IDENTIFIER = re.compile(r"[A-Za-z][A-Za-z0-9_]*\Z")
31
+ _DECLARATION = re.compile(r"^\s*(?:consts|abbreviation|definition|fun|primrec|inductive)\s+"
32
+ r"([A-Za-z][A-Za-z0-9_']*)")
33
+
34
+ #: Names the writers' own text gives a meaning to inside a binder's scope.
35
+ _BUILT_IN = frozenset({"True", "False", "card"})
36
+
37
+
38
+ def declared_names(lines: Iterable[str]) -> FrozenSet[str]:
39
+ """The names that the declarations among ``lines`` (``consts``, ``abbreviation``, …) introduce.
40
+
41
+ These are the symbols of the theory a binder must keep clear of; they are read off the text
42
+ the writer emits, so a symbol added to the vocabulary later is covered without being listed.
43
+ """
44
+ names = set(_BUILT_IN)
45
+ for line in "\n".join(lines).splitlines(): # an element may hold several lines
46
+ match = _DECLARATION.match(line)
47
+ if match:
48
+ names.add(match.group(1))
49
+ return frozenset(names)
50
+
51
+
52
+ def _identifier(name: str) -> str:
53
+ """``name`` as a legal Isabelle identifier: letters, digits and underscores, a letter first."""
54
+ if _IDENTIFIER.match(name):
55
+ return name
56
+ safe = "".join(c if (c.isascii() and (c.isalnum() or c == "_")) else "_" for c in name)
57
+ return safe if safe[:1].isalpha() else "v" + safe
58
+
59
+
60
+ def binder_tokens(binders: Iterable[Tuple[str, str]],
61
+ taken: Iterable[str]) -> Dict[Tuple[str, str], str]:
62
+ """The name each binder is printed under, ``{(kind, name): token}``.
63
+
64
+ ``binders`` are ``(kind, name)`` pairs, ``kind`` being :data:`VARIABLE` or :data:`PREDICATE`;
65
+ ``taken`` are the names of the theory (:func:`declared_names`). The token of a binder is its
66
+ own name (as a legal identifier) unless that is taken, by a symbol or by the token of another
67
+ binder, in which case it is the first of ``name_2``, ``name_3``, … that is not. The order of
68
+ ``binders`` does not matter: they are claimed in sorted order.
69
+ """
70
+ used = set(taken)
71
+ tokens: Dict[Tuple[str, str], str] = {}
72
+ for kind, name in sorted(set(binders), key=lambda b: (b[1], b[0])):
73
+ tokens[(kind, name)] = dedupe(_identifier(name), used)
74
+ return tokens
75
+
76
+
77
+ class BinderScope:
78
+ """The binders in scope at one point of a formula, and the names they are printed under.
79
+
80
+ Immutable: :meth:`enter` returns the scope inside a binder. An occurrence of a variable is
81
+ printed under its binder's token when a binder of that name encloses it, and under
82
+ ``default`` otherwise (a free individual, declared as a constant of its own name).
83
+ """
84
+
85
+ __slots__ = ("_tokens", "_in_scope")
86
+
87
+ def __init__(self, tokens: Mapping[Tuple[str, str], str],
88
+ in_scope: Optional[Mapping[Tuple[str, str], str]] = None):
89
+ self._tokens = tokens
90
+ self._in_scope: Dict[Tuple[str, str], str] = dict(in_scope or {})
91
+
92
+ def enter(self, kind: str, name: str) -> Tuple[str, "BinderScope"]:
93
+ """``(token, scope inside the binder)`` for a binder of ``kind`` and ``name``: its token
94
+ when it has one, its own name when it has none."""
95
+ token = self._tokens.get((kind, name), name)
96
+ inner = dict(self._in_scope)
97
+ inner[(kind, name)] = token
98
+ return token, BinderScope(self._tokens, inner)
99
+
100
+ def token(self, kind: str, name: str, default: Optional[str] = None) -> str:
101
+ """The token of the enclosing binder of ``kind`` and ``name``, else ``default``
102
+ (``name`` itself when no default is given)."""
103
+ found = self._in_scope.get((kind, name))
104
+ if found is not None:
105
+ return found
106
+ return name if default is None else default
107
+
108
+
109
+ def collect_binders(formulas: Iterable[Node]) -> List[Tuple[str, str]]:
110
+ """Every binder of ``formulas``, as ``(kind, name)``.
111
+
112
+ An object quantifier, a cardinality term and a lambda abstraction bind a VARIABLE (the name
113
+ of its ``variable`` or ``param``); a second-order quantifier binds a PREDICATE variable
114
+ (its ``predicate``).
115
+ """
116
+ found: List[Tuple[str, str]] = []
117
+ for formula in formulas:
118
+ for node in formula.walk():
119
+ if isinstance(node, (Quantifier, Cardinality, SortedCardinality)):
120
+ found.append((VARIABLE, node.variable.name))
121
+ elif isinstance(node, Lambda):
122
+ found.append((VARIABLE, node.param.name))
123
+ elif isinstance(node, SecondOrderQuantifier):
124
+ found.append((PREDICATE, node.predicate))
125
+ return found