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,221 @@
1
+ """Rewrite AST symbol names to MSFLParser-legal tokens for a round-trippable render.
2
+
3
+ An imported formula (e.g. from a TPTP file produced by an OWL→FOL translation) can
4
+ carry symbol names that the toolkit's AST accepts but the ``MSFLParser`` *lexer* does
5
+ not — IRIs with underscores and mixed case (``'http___example_org_Thing'`` →
6
+ ``Http___example_org_Thing``), a predicate that starts lower-case, a variable with an
7
+ underscore, and so on. Rendering such a node with :meth:`Node.to_unicode_str` then
8
+ produces a string that does **not** re-parse. A CONSTANT is the exception: the Unicode
9
+ syntax writes a constant of any name in single quotes when its bare word would read as
10
+ something else (``'x1'``, ``'Alice'``, ``'G-910'``), so the text of a constant keeps the
11
+ name as it is, and a quoted constant is the way to keep a name in the Unicode syntax.
12
+
13
+ :func:`sanitize_names` is the other way, and it still renames: it gives every symbol
14
+ name a token that re-parses to its intended class, so
15
+ ``parse(sanitized.to_unicode_str()) == sanitized``, and the names are bare words of
16
+ the plain shapes the ASCII targets take, with a mapping back to the originals:
17
+
18
+ - **predicates** → ``[A-Z][a-zA-Z0-9]*`` (uppercase-initial, alphanumeric);
19
+ - **functions** → a multi-letter lowercase ``NAME``;
20
+ - **constants** → kept verbatim if already a legal bare ``NAME``, else the explicit
21
+ ``c_…`` constant form (so a single-letter or digit-bearing constant such as ``a`` /
22
+ ``x1`` is a bare word that is no variable);
23
+ - **variables** → kept if already ``[a-z][0-9]*``, else mapped to ``v0`` / ``v1`` / …
24
+
25
+ Already-legal names pass through unchanged, so a clean classical formula is untouched.
26
+ Distinct original names always map to distinct legal names (collisions get a numeric
27
+ suffix), so the rewrite is structure-preserving and the returned :class:`NameMapping`
28
+ recovers the originals. Pass the mapping back across calls to keep names consistent over
29
+ a whole problem (the same IRI maps to the same token in every formula).
30
+
31
+ Public API: :func:`sanitize_names`, :func:`sanitize_all`, :class:`NameMapping`.
32
+ """
33
+
34
+ import re
35
+ from dataclasses import dataclass, field
36
+ from typing import Dict, List, Optional, Tuple
37
+
38
+ from .nodes import Node, Variable, Constant, Atom, Function
39
+
40
+ # Operator-valued predicate / function names are rendered as glyphs, not emitted as
41
+ # identifiers, so they are never renamed.
42
+ _COMPARISONS = frozenset({"=", "≠", "<", ">", "≤", "≥"})
43
+ _ARITH = frozenset({"+", "-", "*", "/"})
44
+
45
+ # A bare NAME (function symbol or unmarked constant) in MSFLParser: lowercase-initial,
46
+ # at least two letters. A VARIABLE is a single lowercase letter with an optional digit
47
+ # tail. Used to decide whether a name is already legal in its class.
48
+ _NAME_RE = re.compile(r"[a-z][a-zA-Z0-9]*[a-zA-Z][a-zA-Z0-9]*")
49
+ _VAR_RE = re.compile(r"[a-z][0-9]*")
50
+ _PRED_RE = re.compile(r"[A-Z][a-zA-Z0-9]*")
51
+
52
+
53
+ def _alnum(name: str) -> str:
54
+ """Keep only the ASCII-alphanumeric characters of ``name``."""
55
+ return "".join(ch for ch in name if ("a" <= ch <= "z") or ("A" <= ch <= "Z") or ch.isdigit())
56
+
57
+
58
+ @dataclass
59
+ class NameMapping:
60
+ """The original→legal renamings chosen by :func:`sanitize_names`, per symbol class.
61
+
62
+ Each of ``predicate`` / ``function`` / ``constant`` / ``variable`` maps an original
63
+ name to the legal token it was rewritten to. ``used`` is the set of legal tokens
64
+ already handed out (shared across classes so no two symbols ever collide). Reuse one
65
+ instance across calls to keep names consistent over a whole problem.
66
+ """
67
+
68
+ predicate: Dict[str, str] = field(default_factory=dict)
69
+ function: Dict[str, str] = field(default_factory=dict)
70
+ constant: Dict[str, str] = field(default_factory=dict)
71
+ variable: Dict[str, str] = field(default_factory=dict)
72
+ used: set = field(default_factory=set)
73
+ _var_counter: int = 0
74
+
75
+ def reverse(self) -> Dict[str, str]:
76
+ """Return a flat ``legal → original`` dict over every class (for recovery)."""
77
+ out: Dict[str, str] = {}
78
+ for table in (self.predicate, self.function, self.constant, self.variable):
79
+ for orig, legal in table.items():
80
+ out[legal] = orig
81
+ return out
82
+
83
+ # -- internal --------------------------------------------------------------
84
+ def _reserve(self, candidate: str) -> str:
85
+ """Return ``candidate`` (or ``candidate``+suffix) not yet used; reserve it."""
86
+ if candidate not in self.used:
87
+ self.used.add(candidate)
88
+ return candidate
89
+ i = 2
90
+ while f"{candidate}{i}" in self.used:
91
+ i += 1
92
+ chosen = f"{candidate}{i}"
93
+ self.used.add(chosen)
94
+ return chosen
95
+
96
+ def for_predicate(self, name: str) -> str:
97
+ """Legal predicate token for ``name`` (memoised)."""
98
+ if name in self.predicate:
99
+ return self.predicate[name]
100
+ base = _alnum(name)
101
+ if not base or not base[0].isalpha():
102
+ base = "P" + base
103
+ base = base[0].upper() + base[1:]
104
+ legal = self._reserve(base)
105
+ self.predicate[name] = legal
106
+ return legal
107
+
108
+ def for_function(self, name: str) -> str:
109
+ """Legal function token (multi-letter lowercase NAME) for ``name`` (memoised)."""
110
+ if name in self.function:
111
+ return self.function[name]
112
+ base = _alnum(name)
113
+ if not base or not base[0].isalpha():
114
+ base = "f" + base
115
+ base = base[0].lower() + base[1:]
116
+ if sum(ch.isalpha() for ch in base) < 2: # NAME needs >= 2 letters
117
+ base = base + "fn"
118
+ legal = self._reserve(base)
119
+ self.function[name] = legal
120
+ return legal
121
+
122
+ def for_constant(self, name: str) -> str:
123
+ """Legal constant token for ``name``: a legal bare NAME kept as it is,
124
+ otherwise the ``c_`` form."""
125
+ if name in self.constant:
126
+ return self.constant[name]
127
+ if _NAME_RE.fullmatch(name):
128
+ legal = self._reserve(name) # already a legal bare constant
129
+ else:
130
+ legal = self._reserve("c_" + (_alnum(name) or "0"))
131
+ self.constant[name] = legal
132
+ return legal
133
+
134
+ def for_variable(self, name: str) -> str:
135
+ """Legal variable token for ``name``: keep ``[a-z][0-9]*``, else map to v0/v1/…."""
136
+ if name in self.variable:
137
+ return self.variable[name]
138
+ if _VAR_RE.fullmatch(name):
139
+ legal = self._reserve(name)
140
+ else:
141
+ cand = f"v{self._var_counter}"
142
+ self._var_counter += 1
143
+ while cand in self.used:
144
+ cand = f"v{self._var_counter}"
145
+ self._var_counter += 1
146
+ legal = self._reserve(cand)
147
+ self.variable[name] = legal
148
+ return legal
149
+
150
+
151
+ def _rewrite(node: Node, m: NameMapping) -> Node:
152
+ """Rebuild ``node`` with every symbol name replaced via ``m`` (capture-consistent).
153
+
154
+ Variables are renamed uniformly by name, so a binder's bound variable and its body
155
+ occurrences get the same legal name — binding structure is preserved without
156
+ per-binder special-casing.
157
+ """
158
+ cls = type(node).__name__
159
+ if cls == "Variable":
160
+ return Variable(m.for_variable(node.name))
161
+ if cls == "Constant":
162
+ return Constant(m.for_constant(node.name))
163
+ if cls == "Atom":
164
+ pred = node.predicate if node.predicate in _COMPARISONS else m.for_predicate(node.predicate)
165
+ return Atom(pred, [_rewrite(a, m) for a in node.args])
166
+ if cls == "Function":
167
+ name = node.name if node.name in _ARITH else m.for_function(node.name)
168
+ return Function(name, [_rewrite(a, m) for a in node.args])
169
+ if cls in ("Number", "LambdaVar"):
170
+ return node
171
+ # Every other node (connectives, quantifiers, binders, modal operators, …) is
172
+ # structural: its bound variable (if any) is a Variable child and is renamed
173
+ # uniformly above, so map_children rebuilds it correctly.
174
+ return node.map_children(lambda c: _rewrite(c, m))
175
+
176
+
177
+ def _collect(node: Node, m: NameMapping) -> None:
178
+ """Pre-register every symbol's legal name in first-occurrence order (deterministic)."""
179
+ for n in node.walk():
180
+ cls = type(n).__name__
181
+ if cls == "Atom" and n.predicate not in _COMPARISONS:
182
+ m.for_predicate(n.predicate)
183
+ elif cls == "Function" and n.name not in _ARITH:
184
+ m.for_function(n.name)
185
+ elif cls == "Constant":
186
+ m.for_constant(n.name)
187
+ elif cls == "Variable":
188
+ m.for_variable(n.name)
189
+
190
+
191
+ def sanitize_names(node: Node, mapping: Optional[NameMapping] = None) -> Tuple[Node, NameMapping]:
192
+ """Return ``(sanitized_node, mapping)`` with all names rewritten to legal tokens.
193
+
194
+ ``parse(sanitized_node.to_unicode_str()) == sanitized_node`` holds for the default
195
+ ``MSFLParser`` (FOL mode). Already-legal names are unchanged. Pass the returned
196
+ ``mapping`` back in (``mapping=…``) to keep names consistent across formulas of one
197
+ problem; use :meth:`NameMapping.reverse` to recover the originals.
198
+
199
+ Args:
200
+ node: any AST node (typically a classical FOL formula from an importer).
201
+ mapping: an existing :class:`NameMapping` to extend, or ``None`` for a fresh one.
202
+
203
+ Returns:
204
+ The rewritten node and the (possibly extended) mapping.
205
+ """
206
+ if mapping is None:
207
+ mapping = NameMapping()
208
+ _collect(node, mapping)
209
+ return _rewrite(node, mapping), mapping
210
+
211
+
212
+ def sanitize_all(nodes, mapping: Optional[NameMapping] = None) -> Tuple[List[Node], NameMapping]:
213
+ """Sanitize a sequence of formulas with one shared, consistent :class:`NameMapping`.
214
+
215
+ Returns ``(sanitized_nodes, mapping)``; the same original symbol maps to the same
216
+ legal token in every formula, so a whole TPTP/Prover9 problem stays coherent.
217
+ """
218
+ if mapping is None:
219
+ mapping = NameMapping()
220
+ out = [sanitize_names(n, mapping)[0] for n in nodes]
221
+ return out, mapping
@@ -0,0 +1,79 @@
1
+ """Versioned JSON serialisation envelope for AST nodes.
2
+
3
+ ``Node.to_dict()`` / ``Node.from_dict()`` produce and consume the *bare* node
4
+ dictionaries (``{"_type": ..., ...}``) and remain unversioned — they are the
5
+ recursive building blocks. This module adds the STABLE, VERSIONED envelope that
6
+ external consumers (pipelines, dataset tooling, MCP clients, the CLI's
7
+ ``--to json``) should exchange:
8
+
9
+ {"schema_version": 1, "root": {"_type": "Atom", ...}}
10
+
11
+ ``schema_version`` is bumped only when the bare node format itself changes
12
+ incompatibly (a renamed ``_type``, a removed field, a changed field meaning) —
13
+ NOT on ordinary releases, and never for purely additive node types (an old
14
+ reader that meets an unknown ``_type`` fails loudly either way).
15
+ ``deserialize`` accepts both the envelope and a bare node dict, so data written
16
+ before this module existed keeps loading.
17
+ """
18
+
19
+ from typing import Union
20
+
21
+ from .nodes import Node
22
+
23
+ __all__ = ["SCHEMA_VERSION", "serialize", "deserialize"]
24
+
25
+ # History:
26
+ # 1 — first versioned format (0.20.0). Identical bare-node dicts to every
27
+ # release since to_dict()/from_dict() were introduced; the envelope is new.
28
+ SCHEMA_VERSION = 1
29
+
30
+
31
+ def serialize(node: Node) -> dict:
32
+ """Wrap ``node.to_dict()`` in the versioned envelope.
33
+
34
+ The result is JSON-compatible (``json.dumps`` works on it directly) and is
35
+ the format all new external consumers should store and exchange.
36
+ """
37
+ if not isinstance(node, Node):
38
+ raise TypeError(f"serialize expects a Node, got {type(node).__name__}")
39
+ return {"schema_version": SCHEMA_VERSION, "root": node.to_dict()}
40
+
41
+
42
+ def deserialize(data: Union[dict, str]) -> Node:
43
+ """Reconstruct a node from :func:`serialize` output (or a bare node dict).
44
+
45
+ Accepts three shapes, in order of preference:
46
+
47
+ * the versioned envelope ``{"schema_version": n, "root": {...}}`` —
48
+ rejected with ``ValueError`` if ``n`` is newer than this library
49
+ understands (reading data from the future silently would be worse);
50
+ * a bare ``{"_type": ...}`` node dict (pre-envelope data keeps loading);
51
+ * a JSON string of either of the above.
52
+ """
53
+ if isinstance(data, str):
54
+ import json
55
+ data = json.loads(data)
56
+ if not isinstance(data, dict):
57
+ raise TypeError(f"deserialize expects a dict or JSON string, got {type(data).__name__}")
58
+
59
+ if "schema_version" in data:
60
+ version = data["schema_version"]
61
+ if not isinstance(version, int) or version < 1:
62
+ raise ValueError(f"deserialize: invalid schema_version {version!r}")
63
+ if version > SCHEMA_VERSION:
64
+ raise ValueError(
65
+ f"deserialize: schema_version {version} is newer than this "
66
+ f"unicode-logic-kit understands (max {SCHEMA_VERSION}) — upgrade the library."
67
+ )
68
+ root = data.get("root")
69
+ if not isinstance(root, dict):
70
+ raise ValueError("deserialize: envelope is missing its 'root' node dict.")
71
+ return Node.from_dict(root)
72
+
73
+ if "_type" in data:
74
+ return Node.from_dict(data)
75
+
76
+ raise ValueError(
77
+ "deserialize: dict is neither a versioned envelope (schema_version/root) "
78
+ "nor a bare node dict (_type)."
79
+ )