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.
- unicode_logic_kit/__init__.py +385 -0
- unicode_logic_kit/__main__.py +520 -0
- unicode_logic_kit/_deadline.py +219 -0
- unicode_logic_kit/ace/__init__.py +126 -0
- unicode_logic_kit/ace/_align.py +135 -0
- unicode_logic_kit/ace/chem_lexicon.py +128 -0
- unicode_logic_kit/ace/drs_reader.py +570 -0
- unicode_logic_kit/ace/mapping.py +666 -0
- unicode_logic_kit/ace/reverse_modal.py +138 -0
- unicode_logic_kit/ace/runner.py +551 -0
- unicode_logic_kit/ace/translate.py +452 -0
- unicode_logic_kit/ace/verbalize.py +1070 -0
- unicode_logic_kit/api.py +1284 -0
- unicode_logic_kit/atp/__init__.py +177 -0
- unicode_logic_kit/atp/_ascii_names.py +113 -0
- unicode_logic_kit/atp/_html.py +72 -0
- unicode_logic_kit/atp/_substructural_input.py +228 -0
- unicode_logic_kit/atp/_tff_problem.py +715 -0
- unicode_logic_kit/atp/_tptp_problem.py +1111 -0
- unicode_logic_kit/atp/_writer_support.py +289 -0
- unicode_logic_kit/atp/clingo_backend.py +1180 -0
- unicode_logic_kit/atp/cvc5_backend.py +1385 -0
- unicode_logic_kit/atp/eprover_backend.py +732 -0
- unicode_logic_kit/atp/finite_domain.py +1055 -0
- unicode_logic_kit/atp/fitch.py +1547 -0
- unicode_logic_kit/atp/fitch_search.py +551 -0
- unicode_logic_kit/atp/hets_backend.py +339 -0
- unicode_logic_kit/atp/hybrid_down.py +120 -0
- unicode_logic_kit/atp/incremental.py +250 -0
- unicode_logic_kit/atp/kripke_enum.py +741 -0
- unicode_logic_kit/atp/lambek.py +436 -0
- unicode_logic_kit/atp/leo3_backend.py +332 -0
- unicode_logic_kit/atp/linear.py +738 -0
- unicode_logic_kit/atp/lj.py +705 -0
- unicode_logic_kit/atp/logic_backends.py +566 -0
- unicode_logic_kit/atp/ltl_tableau.py +1084 -0
- unicode_logic_kit/atp/minizinc_backend.py +1402 -0
- unicode_logic_kit/atp/modal_tableau.py +1382 -0
- unicode_logic_kit/atp/nanocop_backend.py +410 -0
- unicode_logic_kit/atp/portfolio.py +489 -0
- unicode_logic_kit/atp/protocol.py +1803 -0
- unicode_logic_kit/atp/prover9_entailment.py +1153 -0
- unicode_logic_kit/atp/resolution.py +1376 -0
- unicode_logic_kit/atp/resolution_check.py +1114 -0
- unicode_logic_kit/atp/sequent.py +1050 -0
- unicode_logic_kit/atp/tableau.py +921 -0
- unicode_logic_kit/atp/tableau_check.py +543 -0
- unicode_logic_kit/atp/tptp_ncl.py +811 -0
- unicode_logic_kit/atp/tptp_tff.py +1546 -0
- unicode_logic_kit/atp/tstp.py +1333 -0
- unicode_logic_kit/atp/tstp_check.py +1096 -0
- unicode_logic_kit/atp/twee_backend.py +236 -0
- unicode_logic_kit/atp/twee_check.py +711 -0
- unicode_logic_kit/atp/twee_entailment.py +953 -0
- unicode_logic_kit/atp/vampire_entailment.py +540 -0
- unicode_logic_kit/atp/z3_arith.py +470 -0
- unicode_logic_kit/atp/z3_equivalence.py +36 -0
- unicode_logic_kit/atp/z3_fuzzy.py +362 -0
- unicode_logic_kit/atp/z3_input.py +500 -0
- unicode_logic_kit/atp/z3_models.py +208 -0
- unicode_logic_kit/chem/__init__.py +88 -0
- unicode_logic_kit/chem/_naming.py +284 -0
- unicode_logic_kit/chem/cache.py +185 -0
- unicode_logic_kit/chem/interop.py +244 -0
- unicode_logic_kit/chem/mol.py +525 -0
- unicode_logic_kit/chem/signature.py +112 -0
- unicode_logic_kit/comorphism.py +497 -0
- unicode_logic_kit/dl/__init__.py +384 -0
- unicode_logic_kit/dl/classification.py +227 -0
- unicode_logic_kit/dl/concepts.py +632 -0
- unicode_logic_kit/dl/datatypes.py +818 -0
- unicode_logic_kit/dl/owl_functional.py +2433 -0
- unicode_logic_kit/dl/owl_manchester.py +1637 -0
- unicode_logic_kit/dl/owl_reasoner.py +790 -0
- unicode_logic_kit/dl/parser.py +391 -0
- unicode_logic_kit/dl/tableau.py +4048 -0
- unicode_logic_kit/dl/translate.py +2704 -0
- unicode_logic_kit/drt/__init__.py +94 -0
- unicode_logic_kit/drt/export.py +179 -0
- unicode_logic_kit/drt/nodes.py +506 -0
- unicode_logic_kit/drt/parser.py +965 -0
- unicode_logic_kit/drt/resolve.py +195 -0
- unicode_logic_kit/drt/reverse.py +175 -0
- unicode_logic_kit/eval/__init__.py +106 -0
- unicode_logic_kit/eval/batch.py +382 -0
- unicode_logic_kit/eval/canonical.py +663 -0
- unicode_logic_kit/eval/chem_batch.py +606 -0
- unicode_logic_kit/eval/converses.py +200 -0
- unicode_logic_kit/eval/datasets/__init__.py +136 -0
- unicode_logic_kit/eval/datasets/_base.py +263 -0
- unicode_logic_kit/eval/datasets/_proofwriter_proof.py +422 -0
- unicode_logic_kit/eval/datasets/c3po.py +678 -0
- unicode_logic_kit/eval/datasets/folio.py +158 -0
- unicode_logic_kit/eval/datasets/fracas.py +418 -0
- unicode_logic_kit/eval/datasets/groves.py +191 -0
- unicode_logic_kit/eval/datasets/logicbench.py +467 -0
- unicode_logic_kit/eval/datasets/logicnli.py +303 -0
- unicode_logic_kit/eval/datasets/malls.py +133 -0
- unicode_logic_kit/eval/datasets/pfolio.py +594 -0
- unicode_logic_kit/eval/datasets/pmb.py +242 -0
- unicode_logic_kit/eval/datasets/prontoqa.py +611 -0
- unicode_logic_kit/eval/datasets/proofwriter.py +1431 -0
- unicode_logic_kit/eval/datasets/proverqa.py +674 -0
- unicode_logic_kit/eval/datasets/willow.py +478 -0
- unicode_logic_kit/eval/equivalence.py +466 -0
- unicode_logic_kit/eval/exercise_gen.py +533 -0
- unicode_logic_kit/eval/explain.py +791 -0
- unicode_logic_kit/eval/generality.py +750 -0
- unicode_logic_kit/eval/metric_hf.py +458 -0
- unicode_logic_kit/eval/predicate_match.py +343 -0
- unicode_logic_kit/eval/theory_check.py +1170 -0
- unicode_logic_kit/eval/validate.py +306 -0
- unicode_logic_kit/fol/__init__.py +177 -0
- unicode_logic_kit/fol/_atom_keys.py +510 -0
- unicode_logic_kit/fol/_fol_nodes.py +3586 -0
- unicode_logic_kit/fol/_free_parameters.py +105 -0
- unicode_logic_kit/fol/_ho_nodes.py +448 -0
- unicode_logic_kit/fol/_hybrid_nodes.py +308 -0
- unicode_logic_kit/fol/_identifiers.py +1091 -0
- unicode_logic_kit/fol/_lambek_nodes.py +112 -0
- unicode_logic_kit/fol/_linear_nodes.py +352 -0
- unicode_logic_kit/fol/_modal_nodes.py +1467 -0
- unicode_logic_kit/fol/_msfl_nodes.py +2196 -0
- unicode_logic_kit/fol/_numeral_symbols.py +231 -0
- unicode_logic_kit/fol/_so_nodes.py +200 -0
- unicode_logic_kit/fol/_symbol_names.py +81 -0
- unicode_logic_kit/fol/_team_nodes.py +181 -0
- unicode_logic_kit/fol/_tptp_symbols.py +551 -0
- unicode_logic_kit/fol/_truth_constants.py +117 -0
- unicode_logic_kit/fol/casl_export.py +1135 -0
- unicode_logic_kit/fol/casl_import.py +929 -0
- unicode_logic_kit/fol/derivation.py +367 -0
- unicode_logic_kit/fol/dialect_detect.py +70 -0
- unicode_logic_kit/fol/dialect_repair.py +537 -0
- unicode_logic_kit/fol/frames.py +637 -0
- unicode_logic_kit/fol/grammars/terminals.lark +31 -0
- unicode_logic_kit/fol/lambda_tools.py +297 -0
- unicode_logic_kit/fol/latex_input.py +429 -0
- unicode_logic_kit/fol/modal_translation.py +944 -0
- unicode_logic_kit/fol/msflparser.py +1033 -0
- unicode_logic_kit/fol/naming.py +422 -0
- unicode_logic_kit/fol/nodes.py +241 -0
- unicode_logic_kit/fol/normalforms.py +492 -0
- unicode_logic_kit/fol/pal.py +287 -0
- unicode_logic_kit/fol/prolog_export.py +566 -0
- unicode_logic_kit/fol/prolog_input.py +505 -0
- unicode_logic_kit/fol/prover9_input.py +1325 -0
- unicode_logic_kit/fol/qml.py +1760 -0
- unicode_logic_kit/fol/qmltp_input.py +525 -0
- unicode_logic_kit/fol/sanitize.py +221 -0
- unicode_logic_kit/fol/serialize.py +79 -0
- unicode_logic_kit/fol/signature.py +1290 -0
- unicode_logic_kit/fol/simplify_check.py +544 -0
- unicode_logic_kit/fol/spans.py +594 -0
- unicode_logic_kit/fol/tptp_input.py +1503 -0
- unicode_logic_kit/fol/tptp_repair.py +941 -0
- unicode_logic_kit/fol/unification.py +157 -0
- unicode_logic_kit/fol/verbalize.py +263 -0
- unicode_logic_kit/hets/__init__.py +163 -0
- unicode_logic_kit/hets/bridge.py +142 -0
- unicode_logic_kit/hets/client.py +748 -0
- unicode_logic_kit/hets/docker.py +420 -0
- unicode_logic_kit/hets/dol.py +712 -0
- unicode_logic_kit/hets/haskell_json.py +355 -0
- unicode_logic_kit/hets/owl_backend.py +794 -0
- unicode_logic_kit/hets/owl_cli.py +598 -0
- unicode_logic_kit/hets/symbols.py +512 -0
- unicode_logic_kit/hol/__init__.py +140 -0
- unicode_logic_kit/hol/_ho_common.py +323 -0
- unicode_logic_kit/hol/_isabelle_binders.py +125 -0
- unicode_logic_kit/hol/classical.py +812 -0
- unicode_logic_kit/hol/deepshallow/__init__.py +45 -0
- unicode_logic_kit/hol/deepshallow/_common.py +177 -0
- unicode_logic_kit/hol/deepshallow/conditional.py +225 -0
- unicode_logic_kit/hol/deepshallow/intuitionistic.py +181 -0
- unicode_logic_kit/hol/deepshallow/modal.py +217 -0
- unicode_logic_kit/hol/deepshallow/qml.py +406 -0
- unicode_logic_kit/hol/deepshallow/relevant.py +206 -0
- unicode_logic_kit/hol/free.py +753 -0
- unicode_logic_kit/hol/goedel.py +336 -0
- unicode_logic_kit/hol/ho_modal.py +1743 -0
- unicode_logic_kit/hol/intuitionistic.py +403 -0
- unicode_logic_kit/hol/isabelle_conditional.py +593 -0
- unicode_logic_kit/hol/isabelle_modal.py +1908 -0
- unicode_logic_kit/hol/isabelle_relevant.py +412 -0
- unicode_logic_kit/hol/isabelle_runner.py +1147 -0
- unicode_logic_kit/hol/isabelle_substructural.py +884 -0
- unicode_logic_kit/hol/lean.py +1018 -0
- unicode_logic_kit/hol/manyvalued.py +921 -0
- unicode_logic_kit/hol/secondorder.py +687 -0
- unicode_logic_kit/hol/thf_modal.py +941 -0
- unicode_logic_kit/hol/thirdorder.py +397 -0
- unicode_logic_kit/ilp/__init__.py +89 -0
- unicode_logic_kit/ilp/readback.py +389 -0
- unicode_logic_kit/ilp/separation.py +153 -0
- unicode_logic_kit/ilp/task.py +730 -0
- unicode_logic_kit/logic.py +163 -0
- unicode_logic_kit/mcp/__init__.py +28 -0
- unicode_logic_kit/mcp/__main__.py +5 -0
- unicode_logic_kit/mcp/chem_tools.py +1031 -0
- unicode_logic_kit/mcp/server.py +2453 -0
- unicode_logic_kit/mcp/syntax_spec.py +681 -0
- unicode_logic_kit/prob/__init__.py +53 -0
- unicode_logic_kit/prob/_bdd.py +225 -0
- unicode_logic_kit/prob/_column_gen.py +668 -0
- unicode_logic_kit/prob/distribution.py +686 -0
- unicode_logic_kit/prob/nilsson.py +470 -0
- unicode_logic_kit/py.typed +0 -0
- unicode_logic_kit/semantics/__init__.py +137 -0
- unicode_logic_kit/semantics/_modal_reject.py +156 -0
- unicode_logic_kit/semantics/action_models.py +466 -0
- unicode_logic_kit/semantics/asp_models.py +1200 -0
- unicode_logic_kit/semantics/conditional.py +580 -0
- unicode_logic_kit/semantics/dynamic_epistemic.py +95 -0
- unicode_logic_kit/semantics/free_logic.py +913 -0
- unicode_logic_kit/semantics/fuzzy.py +384 -0
- unicode_logic_kit/semantics/fuzzy_kripke.py +442 -0
- unicode_logic_kit/semantics/intuitionistic.py +581 -0
- unicode_logic_kit/semantics/kripke.py +1139 -0
- unicode_logic_kit/semantics/manyvalued.py +580 -0
- unicode_logic_kit/semantics/matrix.py +342 -0
- unicode_logic_kit/semantics/model_eval.py +1135 -0
- unicode_logic_kit/semantics/modelfinder.py +1036 -0
- unicode_logic_kit/semantics/nonmonotonic.py +372 -0
- unicode_logic_kit/semantics/relevant.py +331 -0
- unicode_logic_kit/semantics/secondorder.py +657 -0
- unicode_logic_kit/semantics/structures.py +352 -0
- unicode_logic_kit/semantics/tarski.py +975 -0
- unicode_logic_kit/semantics/team.py +315 -0
- unicode_logic_kit/semantics/team_translation.py +416 -0
- unicode_logic_kit/semantics/thirdorder.py +358 -0
- unicode_logic_kit/semantics/tnorm.py +85 -0
- unicode_logic_kit/semantics/truthtable.py +201 -0
- unicode_logic_kit-0.31.0.dist-info/METADATA +333 -0
- unicode_logic_kit-0.31.0.dist-info/RECORD +237 -0
- unicode_logic_kit-0.31.0.dist-info/WHEEL +4 -0
- 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
|
+
)
|