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,712 @@
|
|
|
1
|
+
r"""DOL (Distributed Ontology, Modelling and Specification Language) library
|
|
2
|
+
emission over CASL specs — a thin structuring layer built entirely on top of
|
|
3
|
+
:mod:`unicode_logic_kit.fol.casl_export`, never reimplementing it.
|
|
4
|
+
|
|
5
|
+
`DOL <https://ontohub.org/dol>`_ is CoFI/OntoHub's heterogeneous layer above
|
|
6
|
+
CASL (and OWL, and every other logic Hets understands): a ``library``
|
|
7
|
+
groups several named ``spec`` blocks and lets one spec build on another via
|
|
8
|
+
structuring (``spec B = A then ...``). :mod:`unicode_logic_kit.fol.casl_export`
|
|
9
|
+
already renders one classical/many-sorted FOL formula batch as one
|
|
10
|
+
self-contained ``spec ... end`` block; this module's only job is to emit
|
|
11
|
+
SEVERAL such blocks inside one ``library``/``logic CASL`` wrapper, with an
|
|
12
|
+
optional ``then``-extension between them. It does not add a single new
|
|
13
|
+
formula-rendering rule of its own — every axiom/conjecture line in the
|
|
14
|
+
output comes verbatim from :func:`~unicode_logic_kit.fol.casl_export.to_casl_spec`.
|
|
15
|
+
|
|
16
|
+
Scope: emission only — no ``parse_dol_library``
|
|
17
|
+
------------------------------------------------
|
|
18
|
+
This module is one-way, like :mod:`~unicode_logic_kit.fol.casl_export` before
|
|
19
|
+
it added an importer counterpart
|
|
20
|
+
(:mod:`unicode_logic_kit.fol.casl_import`) — going the other way (parsing a
|
|
21
|
+
full DOL library text, with its ``library``/``logic``/multi-``spec``/
|
|
22
|
+
``then`` structuring, back into per-spec ASTs) is a materially bigger
|
|
23
|
+
grammar than :func:`~unicode_logic_kit.fol.casl_import.parse_casl_spec`
|
|
24
|
+
already refuses to attempt for a SINGLE spec (see that module's own "Scope"
|
|
25
|
+
section — ``then``/``view``/structuring constructs are explicitly out of
|
|
26
|
+
its fragment). A ``parse_dol_library`` is future scope, not started here;
|
|
27
|
+
:func:`unicode_logic_kit.fol.casl_import.parse_casl_spec` remains usable
|
|
28
|
+
directly on any ONE ``spec ... end`` block sliced out of this module's own
|
|
29
|
+
output (see ``tests/test_dol.py``'s offline consistency check, which does
|
|
30
|
+
exactly that as an export/import round-trip sanity test).
|
|
31
|
+
|
|
32
|
+
Reuse strategy: cut ``to_casl_spec``'s own output, do not re-derive it
|
|
33
|
+
------------------------------------------------------------------------
|
|
34
|
+
:func:`to_dol_library` calls the PUBLIC
|
|
35
|
+
:func:`~unicode_logic_kit.fol.casl_export.to_casl_spec` once per
|
|
36
|
+
:class:`DolSpec` — never touching that module's private internals — and
|
|
37
|
+
then deterministically slices the BODY (the ``sorts``/``ops``/``preds``/
|
|
38
|
+
axiom lines) out of its result: ``to_casl_spec``'s own documented output
|
|
39
|
+
shape is always exactly one header line (``spec <NAME> =``), the body, and
|
|
40
|
+
one trailing ``end`` line (see that function's docstring), so
|
|
41
|
+
``body_text.split("\n")[1:-1]`` is exactly the body, verbatim, no matter how
|
|
42
|
+
many sorts/ops/preds/axiom lines it has. This module then re-wraps that same
|
|
43
|
+
body under EITHER the same plain header (no ``extends``) or a
|
|
44
|
+
``spec <name> = <extends> then`` header (with ``extends``) — see
|
|
45
|
+
:func:`_spec_block` for the one place this slice-and-rewrap happens, guarded
|
|
46
|
+
by an assertion that the expected header/footer shape actually held (so a
|
|
47
|
+
future change to ``to_casl_spec``'s own output shape fails LOUDLY here
|
|
48
|
+
rather than silently emitting a malformed library).
|
|
49
|
+
|
|
50
|
+
``extends`` and signature re-declaration — a deliberate, harmless
|
|
51
|
+
consequence of per-spec-independent inference
|
|
52
|
+
--------------------------------------------------------------------------
|
|
53
|
+
``to_casl_spec`` infers its ``sorts``/``ops``/``preds`` declarations from
|
|
54
|
+
ONLY the formulas passed to that one call — it has no notion of "these
|
|
55
|
+
symbols are already declared by the spec I ``then``-extend". So if spec
|
|
56
|
+
``B`` (``DolSpec(extends="A", ...)``) reuses a predicate or constant that
|
|
57
|
+
spec ``A`` already declared, ``B``'s own emitted block RE-DECLARES it too.
|
|
58
|
+
This is valid, unremarkable CASL: re-declaring an already-visible symbol
|
|
59
|
+
with an IDENTICAL profile (same arity/argument sorts/result sort) under
|
|
60
|
+
``then`` is a no-op in CASL's static semantics, not a conflict — Hets
|
|
61
|
+
accepts it. It only becomes a genuine problem if the SAME symbol name ends
|
|
62
|
+
up with two DIFFERENT profiles across ``A`` and ``B`` (e.g. different
|
|
63
|
+
``default_sort`` values producing different inferred sorts for the same
|
|
64
|
+
predicate) — that is a real modelling error on the caller's part, and
|
|
65
|
+
surfaces exactly the way any other CASL redeclaration conflict would (a
|
|
66
|
+
Hets-side parse/static-analysis error), not something this module tries to
|
|
67
|
+
pre-empt. Callers who want a shared vocabulary should keep every
|
|
68
|
+
:class:`DolSpec` in the same library on the same ``default_sort``.
|
|
69
|
+
|
|
70
|
+
A spec that extends another SEES the other's symbols without declaring them, and a bound
|
|
71
|
+
variable that is spelled like a visible symbol would be read as that variable. So a
|
|
72
|
+
bound variable of the extending spec is never given the spelling of a symbol of the
|
|
73
|
+
specs it extends (found through ``specs``, transitively): the extending spec's
|
|
74
|
+
``to_casl_spec`` call is given those names as ``visible_symbols`` and renames the
|
|
75
|
+
clashing binders, as it does for the spec's own symbols.
|
|
76
|
+
|
|
77
|
+
What is NOT re-checked here
|
|
78
|
+
------------------------------
|
|
79
|
+
* Referential integrity of ``extends``: this module does not verify that a
|
|
80
|
+
``DolSpec.extends`` name is itself a key of ``specs`` (or defined
|
|
81
|
+
anywhere at all) — a library legitimately may extend a spec defined in
|
|
82
|
+
ANOTHER library/import this module knows nothing about. That is the
|
|
83
|
+
caller's responsibility; a dangling reference surfaces as a Hets-side
|
|
84
|
+
error when the library is actually uploaded, not here.
|
|
85
|
+
* Formula validity: every :func:`~unicode_logic_kit.fol.casl_export.to_casl_spec`
|
|
86
|
+
refusal (an out-of-fragment node, a free variable, a sort conflict, a
|
|
87
|
+
reserved-word identifier, an empty axiom+conjecture batch, …) propagates
|
|
88
|
+
through :func:`to_dol_library` UNCHANGED — this module adds no new
|
|
89
|
+
formula-level validation of its own, by design (single source of truth).
|
|
90
|
+
|
|
91
|
+
Name validation
|
|
92
|
+
----------------
|
|
93
|
+
The library name and every ``DolSpec.extends`` target name are checked
|
|
94
|
+
against the same simple-CASL-identifier pattern and reserved-keyword list
|
|
95
|
+
:mod:`~unicode_logic_kit.fol.casl_export` checks ``spec_name`` against (a
|
|
96
|
+
small, independent literal copy of that list — the same "duplicate by
|
|
97
|
+
content, not by importing a private name" reasoning
|
|
98
|
+
:mod:`unicode_logic_kit.fol.signature` and
|
|
99
|
+
:mod:`unicode_logic_kit.fol.casl_import` give for their own copies). Each
|
|
100
|
+
:class:`DolSpec`'s own key in ``specs`` is NOT separately checked here — it
|
|
101
|
+
is passed straight through as ``spec_name`` to
|
|
102
|
+
:func:`~unicode_logic_kit.fol.casl_export.to_casl_spec`, which already
|
|
103
|
+
performs that exact check (:func:`~unicode_logic_kit.fol.casl_export._validate_spec_name`)
|
|
104
|
+
and raises ``ValueError`` itself; duplicating it here would just be a second
|
|
105
|
+
copy of the same regex answering the same question.
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
from collections import OrderedDict
|
|
109
|
+
from dataclasses import dataclass, field
|
|
110
|
+
from typing import Optional, Sequence
|
|
111
|
+
import re
|
|
112
|
+
|
|
113
|
+
from ..fol.casl_export import to_casl_spec
|
|
114
|
+
from ..fol.nodes import Atom, Constant, Function, Node, SortedConstant, Variable
|
|
115
|
+
from ..fol._symbol_names import dedupe
|
|
116
|
+
from ..fol.qml import qml_validity_formula
|
|
117
|
+
|
|
118
|
+
__all__ = [
|
|
119
|
+
"to_dol_library", "DolSpec",
|
|
120
|
+
"to_dol_library_from_modal", "sanitize_modal_identifiers",
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# Mirrors unicode_logic_kit.fol.casl_export._CASL_KEYWORDS /
|
|
125
|
+
# unicode_logic_kit.fol.casl_import._CASL_KEYWORDS — a small, independent
|
|
126
|
+
# literal copy (see the module docstring's "Name validation" section for why
|
|
127
|
+
# this is not an import of another module's private name).
|
|
128
|
+
_CASL_KEYWORDS = frozenset({
|
|
129
|
+
"and", "arch", "as", "assoc", "axiom", "axioms", "closed", "comm", "def",
|
|
130
|
+
"else", "end", "exists", "false", "fit", "forall", "free", "from",
|
|
131
|
+
"generated", "get", "given", "hide", "idem", "if", "in", "lambda",
|
|
132
|
+
"library", "local", "logic", "not", "op", "ops", "pred", "preds",
|
|
133
|
+
"result", "reveal", "sort", "sorts", "spec", "then", "to", "true",
|
|
134
|
+
"type", "types", "unit", "units", "var", "vars", "version", "view",
|
|
135
|
+
"when", "with", "within",
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
_SIMPLE_ID_RE = re.compile(r"[A-Za-z][A-Za-z0-9_]*")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _check_casl_word(name: str, kind: str) -> None:
|
|
142
|
+
"""Refuse ``name`` (a ``kind`` identifier) if it is not a simple CASL
|
|
143
|
+
word, or collides with a CASL keyword — see the module docstring's
|
|
144
|
+
"Name validation" section."""
|
|
145
|
+
if not _SIMPLE_ID_RE.fullmatch(name):
|
|
146
|
+
raise ValueError(
|
|
147
|
+
f"hets.dol: {kind} {name!r} is not a simple CASL identifier "
|
|
148
|
+
"matching [A-Za-z][A-Za-z0-9_]*."
|
|
149
|
+
)
|
|
150
|
+
if name in _CASL_KEYWORDS:
|
|
151
|
+
raise ValueError(
|
|
152
|
+
f"hets.dol: {kind} name '{name}' collides with the reserved "
|
|
153
|
+
f"CASL keyword '{name}'."
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@dataclass
|
|
158
|
+
class DolSpec:
|
|
159
|
+
"""One ``spec`` block's worth of input to :func:`to_dol_library`.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
axioms: the spec's own axioms (rendered as ``. <formula>`` lines,
|
|
163
|
+
exactly as :func:`~unicode_logic_kit.fol.casl_export.to_casl_spec`
|
|
164
|
+
would for a standalone spec).
|
|
165
|
+
conjectures: the spec's own ``%implied`` goals. Defaults to empty —
|
|
166
|
+
a spec that only extends another and adds axioms (no new goal)
|
|
167
|
+
is common.
|
|
168
|
+
extends: the name of another spec this one structurally extends via
|
|
169
|
+
CASL's ``then`` — renders the header as
|
|
170
|
+
``spec <this_name> = <extends> then`` instead of plain
|
|
171
|
+
``spec <this_name> =``. ``None`` (the default) renders a plain,
|
|
172
|
+
unstructured spec. Not checked for referential integrity against
|
|
173
|
+
the other keys of the ``specs`` mapping passed to
|
|
174
|
+
:func:`to_dol_library` — see that function's module-level
|
|
175
|
+
docstring section "What is NOT re-checked here".
|
|
176
|
+
default_sort: passed straight through to this spec's own
|
|
177
|
+
:func:`~unicode_logic_kit.fol.casl_export.to_casl_spec` call —
|
|
178
|
+
see that function's docstring for what it controls (the sort a
|
|
179
|
+
plain, unsorted quantifier is declared at).
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
axioms: Sequence[Node]
|
|
183
|
+
conjectures: Sequence[Node] = field(default_factory=tuple)
|
|
184
|
+
extends: Optional[str] = None
|
|
185
|
+
default_sort: str = "Thing"
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _names_of(spec: DolSpec) -> set:
|
|
189
|
+
"""Every symbol name of ``spec``'s own formulas: constants, functions, predicates, the
|
|
190
|
+
sorts they write, and the spec's default sort (an over-approximation is harmless: the
|
|
191
|
+
names only keep a bound variable away)."""
|
|
192
|
+
names = {spec.default_sort}
|
|
193
|
+
for formula in (*spec.axioms, *spec.conjectures):
|
|
194
|
+
for node in formula.walk():
|
|
195
|
+
if isinstance(node, (Constant, SortedConstant, Function)):
|
|
196
|
+
names.add(node.name)
|
|
197
|
+
elif isinstance(node, Atom):
|
|
198
|
+
names.add(node.predicate)
|
|
199
|
+
sort = getattr(node, "sort", None)
|
|
200
|
+
if isinstance(sort, str):
|
|
201
|
+
names.add(sort)
|
|
202
|
+
return names
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _inherited_names(spec: DolSpec, specs: "OrderedDict[str, DolSpec]") -> frozenset:
|
|
206
|
+
"""The symbol names ``spec`` sees from the specs it extends (``then``), transitively.
|
|
207
|
+
|
|
208
|
+
An extended spec that is not a key of ``specs`` is defined elsewhere and cannot be
|
|
209
|
+
read here (see the module docstring's "What is NOT re-checked here")."""
|
|
210
|
+
names: set = set()
|
|
211
|
+
seen: set = set()
|
|
212
|
+
parent = spec.extends
|
|
213
|
+
while parent is not None and parent in specs and parent not in seen:
|
|
214
|
+
seen.add(parent)
|
|
215
|
+
names |= _names_of(specs[parent])
|
|
216
|
+
parent = specs[parent].extends
|
|
217
|
+
return frozenset(names)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _spec_block(spec_name: str, spec: DolSpec, inherited: frozenset = frozenset()) -> str:
|
|
221
|
+
"""Render one ``spec <spec_name> = [<extends> then] ... end`` block by
|
|
222
|
+
slicing the body out of a fresh :func:`~unicode_logic_kit.fol.casl_export.to_casl_spec`
|
|
223
|
+
call — see the module docstring's "Reuse strategy" section. ``inherited`` are the
|
|
224
|
+
symbol names the spec sees from the specs it extends: a bound variable is never
|
|
225
|
+
given the spelling of one of them (``to_casl_spec``'s ``visible_symbols``)."""
|
|
226
|
+
body_text = to_casl_spec(
|
|
227
|
+
list(spec.axioms), conjectures=list(spec.conjectures),
|
|
228
|
+
spec_name=spec_name, default_sort=spec.default_sort,
|
|
229
|
+
visible_symbols=inherited,
|
|
230
|
+
)
|
|
231
|
+
body_lines = body_text.split("\n")
|
|
232
|
+
expected_header = f"spec {spec_name} ="
|
|
233
|
+
if len(body_lines) < 2 or body_lines[0] != expected_header or body_lines[-1] != "end":
|
|
234
|
+
# Defensive: guards the slicing assumption above against a future
|
|
235
|
+
# (unanticipated) change to to_casl_spec's output shape — fail
|
|
236
|
+
# loudly here rather than silently emit a malformed library.
|
|
237
|
+
raise AssertionError(
|
|
238
|
+
"hets.dol: to_casl_spec's output shape did not match the "
|
|
239
|
+
f"expected 'spec {spec_name} = ... end' envelope this module's "
|
|
240
|
+
f"body-slicing logic assumes; got first line {body_lines[0]!r} "
|
|
241
|
+
f"and last line {body_lines[-1]!r}."
|
|
242
|
+
)
|
|
243
|
+
body = body_lines[1:-1]
|
|
244
|
+
|
|
245
|
+
if spec.extends is not None:
|
|
246
|
+
_check_casl_word(spec.extends, "extends target name")
|
|
247
|
+
header = f"spec {spec_name} = {spec.extends} then"
|
|
248
|
+
else:
|
|
249
|
+
header = expected_header
|
|
250
|
+
|
|
251
|
+
return "\n".join([header, *body, "end"])
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def to_dol_library(name: str, specs: "OrderedDict[str, DolSpec]") -> str:
|
|
255
|
+
"""Render ``specs`` as one DOL library: ``library <name>`` / ``logic
|
|
256
|
+
CASL`` / one ``spec … end`` block per entry, in ``specs`` iteration
|
|
257
|
+
order.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
name: the library's own name — checked against the same simple-
|
|
261
|
+
CASL-identifier + reserved-keyword rule as every other name
|
|
262
|
+
this module or :mod:`~unicode_logic_kit.fol.casl_export` emits.
|
|
263
|
+
specs: an ordered mapping from spec name to :class:`DolSpec`. Order
|
|
264
|
+
matters for the emitted text (specs are written out in
|
|
265
|
+
iteration order) and, practically, for validity: a
|
|
266
|
+
``DolSpec.extends`` reference should generally name a spec that
|
|
267
|
+
appears EARLIER in this mapping (or is defined elsewhere), since
|
|
268
|
+
CASL structuring is not mutually recursive — this module does
|
|
269
|
+
not enforce that ordering itself (see the module docstring's
|
|
270
|
+
"What is NOT re-checked here").
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
The complete library text (no trailing newline, matching
|
|
274
|
+
:func:`~unicode_logic_kit.fol.casl_export.to_casl_spec`'s own
|
|
275
|
+
convention), ready to upload as a ``.dol`` file via
|
|
276
|
+
:meth:`unicode_logic_kit.hets.HetsClient.upload`.
|
|
277
|
+
|
|
278
|
+
Raises:
|
|
279
|
+
ValueError: ``specs`` is empty; ``name`` or any
|
|
280
|
+
``DolSpec.extends`` value is not a simple CASL identifier or
|
|
281
|
+
collides with a CASL keyword; or (propagated, unchanged) any
|
|
282
|
+
:func:`~unicode_logic_kit.fol.casl_export.to_casl_spec` refusal
|
|
283
|
+
for one of the individual specs (an out-of-fragment node, a
|
|
284
|
+
free variable, a sort conflict, a bad ``spec_name``, an empty
|
|
285
|
+
axiom+conjecture batch, …).
|
|
286
|
+
NotImplementedError: propagated, unchanged, from
|
|
287
|
+
:func:`~unicode_logic_kit.fol.casl_export.to_casl_spec` (a
|
|
288
|
+
non-classical node in one of the specs' formulas).
|
|
289
|
+
"""
|
|
290
|
+
_check_casl_word(name, "library name")
|
|
291
|
+
if not specs:
|
|
292
|
+
raise ValueError(
|
|
293
|
+
"hets.dol: to_dol_library needs at least one spec in 'specs'; "
|
|
294
|
+
"an empty library has nothing to structure."
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
lines = [f"library {name}", "logic CASL", ""]
|
|
298
|
+
items = list(specs.items())
|
|
299
|
+
for idx, (spec_name, spec) in enumerate(items):
|
|
300
|
+
lines.append(_spec_block(spec_name, spec, _inherited_names(spec, specs)))
|
|
301
|
+
if idx != len(items) - 1:
|
|
302
|
+
lines.append("")
|
|
303
|
+
return "\n".join(lines)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
# =============================================================================
|
|
307
|
+
# Quantified modal logic -> CASL/DOL, over fol.qml's standard translation
|
|
308
|
+
# =============================================================================
|
|
309
|
+
#
|
|
310
|
+
# fol.qml.qml_validity_formula already lowers a modal formula to exactly the
|
|
311
|
+
# classical FOL fragment fol.casl_export accepts (see that function's own
|
|
312
|
+
# docstring) — this section's job is the ONE concrete gap between
|
|
313
|
+
# "qml_validity_formula's output" and "a formula to_casl_spec will actually
|
|
314
|
+
# accept", point (1); point (2) records the second place one might expect a
|
|
315
|
+
# gap, object identity, and why there is none:
|
|
316
|
+
#
|
|
317
|
+
# (1) Illegal identifiers. qml's own auto-generated fresh variables (the
|
|
318
|
+
# world variables _Fresh mints, e.g. "_w0", and the Geach axiom's own
|
|
319
|
+
# "_gz0"/"_gw"/"_gu"/"_gv"/"_gt", and the argument variables
|
|
320
|
+
# _signature_typing_facts mints for a function's typing fact, "_a0") start
|
|
321
|
+
# with an underscore, which is not a legal CASL SIMPLE-ID
|
|
322
|
+
# ([A-Za-z][A-Za-z0-9_]*) — casl_export's own honesty convention is to
|
|
323
|
+
# REFUSE a bad identifier rather than silently rename it (see its "Reserved
|
|
324
|
+
# words and identifier hygiene" docstring section), which is exactly right
|
|
325
|
+
# for a name the CALLER chose, but qml's fresh names are an internal
|
|
326
|
+
# implementation detail of the translation, not something a caller of this
|
|
327
|
+
# bridge ever typed — so THIS module fixes them before they ever reach
|
|
328
|
+
# casl_export, rather than asking every caller of qml_validity_formula to
|
|
329
|
+
# pre-empt a naming convention that is qml's own.
|
|
330
|
+
#
|
|
331
|
+
# (2) Object identity needs no help: it arrives binary and CASL-native.
|
|
332
|
+
# fol.qml's ``_st`` translates an identity atom ``a = b`` to the SAME binary
|
|
333
|
+
# ``a = b`` over the object terms, with NO world argument, and ``a ≠ b`` to
|
|
334
|
+
# ``¬(a = b)`` (see that module's "Equality is rigid" section). Identity does
|
|
335
|
+
# not vary by world, and the translation says so by not mentioning the world.
|
|
336
|
+
# That binary ``=`` is exactly CASL's own built-in equality — always two
|
|
337
|
+
# terms, rigid, never declared in ``preds`` — so fol.casl_export renders it
|
|
338
|
+
# infix (``a = b``), fol.casl_import parses it back to the identical Atom, and
|
|
339
|
+
# nothing in this module renames, aliases or special-cases it. The one other
|
|
340
|
+
# ``=`` atom a qml query can contain, the world identity ``w = v`` in qml's
|
|
341
|
+
# own temporal ``first_step`` axiom, is the same native symbol: it relates two
|
|
342
|
+
# worlds where a user equality relates objects, and what keeps the two kinds
|
|
343
|
+
# apart is the ``World``/``Object`` guards, not the symbol. ``≠`` never reaches
|
|
344
|
+
# this module at all, because qml has already written it as a negation of
|
|
345
|
+
# ``=``, which CASL renders like any other ``not``.
|
|
346
|
+
#
|
|
347
|
+
# So :func:`_rewrite_casl_names` leaves every ``=`` atom under its literal
|
|
348
|
+
# name and refuses every ``≠`` atom. Both matter only for a Node that did NOT
|
|
349
|
+
# come out of qml_validity_formula, i.e. one built by hand and handed to
|
|
350
|
+
# :func:`sanitize_modal_identifiers` directly (no text front-end of the kit
|
|
351
|
+
# parses to a non-binary ``=``/``≠``, and qml_validity_formula itself refuses
|
|
352
|
+
# one with a ValueError):
|
|
353
|
+
#
|
|
354
|
+
# * a ``=`` atom that is not binary is not CASL's identity and is not
|
|
355
|
+
# renamed onto some uninterpreted predicate either: it is passed through
|
|
356
|
+
# unchanged, so fol.casl_export's own exactly-two-terms check refuses it by
|
|
357
|
+
# name (one refusal, one place);
|
|
358
|
+
# * a ``≠`` atom of ANY arity is refused here with :class:`NotImplementedError`:
|
|
359
|
+
# CASL has no disequality, and the sanitiser renames identifiers, it does not
|
|
360
|
+
# rewrite connectives, so the caller writes ``Not(Atom("=", ...))``.
|
|
361
|
+
#
|
|
362
|
+
# History, because the symbols below used to exist: fol.qml once appended the
|
|
363
|
+
# world to ``=`` like to any atom, which made it a ternary uninterpreted
|
|
364
|
+
# predicate, and this module aliased it to ``weq``/``wneq``. Both are gone
|
|
365
|
+
# together; an atom literally named ``weq`` is now an ordinary user predicate
|
|
366
|
+
# like any other.
|
|
367
|
+
#
|
|
368
|
+
# No change to fol.qml's or fol.casl_export's own logic: qml_validity_formula
|
|
369
|
+
# is called unmodified, casl_export's own exactly-2-ary "=" check is never
|
|
370
|
+
# touched, and the sanitised Node is handed to to_casl_spec via
|
|
371
|
+
# to_dol_library exactly as any other DolSpec's axioms/conjectures would be.
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def _casl_sanitize_stem(name: str) -> str:
|
|
375
|
+
"""A best-effort CASL-legal *candidate* for ``name`` — not yet guaranteed
|
|
376
|
+
unique (see :class:`_CaslIdentifierShim`, which wraps this in
|
|
377
|
+
:func:`~unicode_logic_kit.fol._symbol_names.dedupe` against every other name
|
|
378
|
+
already claimed in the same namespace, so injectivity is enforced exactly
|
|
379
|
+
once, in one place).
|
|
380
|
+
|
|
381
|
+
Strips every LEADING underscore first — the shape this shim was written for
|
|
382
|
+
(see the section comment above): the fresh names
|
|
383
|
+
:mod:`unicode_logic_kit.fol.qml` used to generate were ``_w0`` / ``_gz0`` /
|
|
384
|
+
``_a0``, each an ordinary, already-legal identifier once that convention is
|
|
385
|
+
peeled off. Since 0.30.0 that module mints ``w0`` / ``v0`` style names
|
|
386
|
+
instead (they have to be legal for the KIT's own parser too), so no CALLER
|
|
387
|
+
in this repository still hands this function such a name — the rule stays
|
|
388
|
+
for any other one that might. What is left over is then re-checked against
|
|
389
|
+
CASL's own SIMPLE-ID grammar, and this is the part that is still reached:
|
|
390
|
+
a candidate that does not start with an ASCII letter (all-underscore, or
|
|
391
|
+
digit-leading) is prefixed with ``v``, and any remaining illegal character
|
|
392
|
+
is folded to ``_`` — which is what happens to ``fol.qml``'s ``·`` mark on a
|
|
393
|
+
user predicate named like one of its own relations (``R`` -> ``R·`` ->
|
|
394
|
+
``R_``), the one shape of illegal CASL identifier this route still emits.
|
|
395
|
+
"""
|
|
396
|
+
candidate = name.lstrip("_") or "id"
|
|
397
|
+
if not (candidate[0].isascii() and candidate[0].isalpha()):
|
|
398
|
+
candidate = "v" + candidate
|
|
399
|
+
return "".join(
|
|
400
|
+
ch if (ch.isascii() and (ch.isalnum() or ch == "_")) else "_"
|
|
401
|
+
for ch in candidate)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
class _CaslIdentifierShim:
|
|
405
|
+
"""Per-formula, INJECTIVE renamer: maps every Variable / (Sorted)Constant-
|
|
406
|
+
or-Function / Atom-predicate name onto a legal, unique CASL identifier,
|
|
407
|
+
leaving every name that already qualifies untouched.
|
|
408
|
+
|
|
409
|
+
Three separate TABLES, one per kind of symbol: ``variable`` (bound
|
|
410
|
+
object/world variables), ``term`` (constants AND functions share ONE
|
|
411
|
+
table here, mirroring
|
|
412
|
+
:func:`~unicode_logic_kit.fol.casl_export.to_casl_spec`'s own merged
|
|
413
|
+
``ops`` declaration block and its ``_CONST_VS_FUNCTION`` dual-use
|
|
414
|
+
refusal), and ``predicate`` (Atom predicate names). A name is mapped only
|
|
415
|
+
within its own table, so a variable and a constant that already are legal
|
|
416
|
+
keep their spelling even when it is the same one; the text then says the
|
|
417
|
+
two apart by renaming the binder (see
|
|
418
|
+
:func:`~unicode_logic_kit.fol.casl_export.to_casl_spec`). A name the shim
|
|
419
|
+
MINTS, on the other hand, is drawn from ONE pool of taken spellings shared
|
|
420
|
+
by the three tables and the sorts of the formula, because CASL writes all
|
|
421
|
+
of them as one identifier: a renamed variable is never given the spelling
|
|
422
|
+
of a constant, a function, a predicate or a sort. ``=`` is not in any
|
|
423
|
+
of them: it is CASL's own native, rigid equality and not a declared
|
|
424
|
+
symbol, so :func:`_rewrite_casl_names` keeps it under its literal name
|
|
425
|
+
and it is never a simple identifier for :meth:`_seed` to claim; ``≠`` is
|
|
426
|
+
refused there before it could be renamed. See the section comment above,
|
|
427
|
+
point (2).
|
|
428
|
+
|
|
429
|
+
TWO PASSES per namespace, in that order, is what makes this injective
|
|
430
|
+
regardless of tree-walk order: :meth:`__init__` first walks the WHOLE
|
|
431
|
+
formula and seeds every name that is ALREADY a legal CASL identifier —
|
|
432
|
+
self-mapped, claimed in that namespace's ``used`` set — before any
|
|
433
|
+
illegal name is resolved. Only then does :meth:`_resolve` compute a
|
|
434
|
+
sanitised candidate for an illegal name and hand it to
|
|
435
|
+
:func:`~unicode_logic_kit.fol._symbol_names.dedupe`, which appends a
|
|
436
|
+
numeric suffix on any collision — including a collision with an
|
|
437
|
+
ALREADY-legal name from the seeding pass. This order is load-bearing:
|
|
438
|
+
the fresh variable ``_w0`` sanitises to the natural candidate ``w0`` via
|
|
439
|
+
:func:`_casl_sanitize_stem`, and if the SAME formula also happens to
|
|
440
|
+
bind a genuine, unrelated object variable literally named ``w0`` (an
|
|
441
|
+
already-legal name — this is the exact collision batch note (3) asks to
|
|
442
|
+
be tested), seeding it FIRST means ``_w0``'s candidate is found already
|
|
443
|
+
taken and gets bumped to ``w0_2`` — two DISTINCT source names, two
|
|
444
|
+
DISTINCT output names — rather than the two silently merging into one
|
|
445
|
+
bound variable (a single-pass, seed-as-you-go walk could not guarantee
|
|
446
|
+
this, since it would depend on which of the two names the tree happens
|
|
447
|
+
to visit first).
|
|
448
|
+
|
|
449
|
+
Deliberately narrow: an already-legal name that happens to collide with
|
|
450
|
+
a CASL KEYWORD (e.g. a user constant literally named ``type``) is left
|
|
451
|
+
untouched here and surfaces exactly as it always did — a loud
|
|
452
|
+
:class:`ValueError` from :func:`~unicode_logic_kit.fol.casl_export.to_casl_spec`
|
|
453
|
+
itself — because that is a naming choice the CALLER made, not qml's own
|
|
454
|
+
fresh-name convention, and this shim exists to fix the latter, not to
|
|
455
|
+
silently rename the former (see the section comment above).
|
|
456
|
+
"""
|
|
457
|
+
|
|
458
|
+
def __init__(self, formula: Node):
|
|
459
|
+
self._var, self._term, self._pred = {}, {}, {}
|
|
460
|
+
# The three tables map names of three kinds, but CASL writes all of them as one
|
|
461
|
+
# identifier, so the three kinds draw the names they mint from ONE set of taken
|
|
462
|
+
# spellings: a minted name is fresh against every symbol of the formula, not
|
|
463
|
+
# only against the other symbols of its own kind.
|
|
464
|
+
taken: set = set()
|
|
465
|
+
self._var_used = self._term_used = self._pred_used = taken
|
|
466
|
+
for n in formula.walk():
|
|
467
|
+
if isinstance(n, Variable):
|
|
468
|
+
self._seed(self._var, self._var_used, n.name)
|
|
469
|
+
elif isinstance(n, (Constant, SortedConstant, Function)):
|
|
470
|
+
self._seed(self._term, self._term_used, n.name)
|
|
471
|
+
elif isinstance(n, Atom):
|
|
472
|
+
# '=' and '≠' are not simple identifiers, so _seed ignores them.
|
|
473
|
+
self._seed(self._pred, self._pred_used, n.predicate)
|
|
474
|
+
sort = getattr(n, "sort", None)
|
|
475
|
+
if isinstance(sort, str):
|
|
476
|
+
# a sort a node names is a spelling taken too (it has no table here)
|
|
477
|
+
taken.add(sort)
|
|
478
|
+
|
|
479
|
+
@staticmethod
|
|
480
|
+
def _seed(table: dict, used: set, name: str) -> None:
|
|
481
|
+
if name not in table and _SIMPLE_ID_RE.fullmatch(name):
|
|
482
|
+
table[name] = name
|
|
483
|
+
used.add(name)
|
|
484
|
+
|
|
485
|
+
@staticmethod
|
|
486
|
+
def _resolve(table: dict, used: set, name: str) -> str:
|
|
487
|
+
if name in table:
|
|
488
|
+
return table[name]
|
|
489
|
+
result = dedupe(_casl_sanitize_stem(name), used)
|
|
490
|
+
table[name] = result
|
|
491
|
+
return result
|
|
492
|
+
|
|
493
|
+
def variable(self, name: str) -> str:
|
|
494
|
+
return self._resolve(self._var, self._var_used, name)
|
|
495
|
+
|
|
496
|
+
def term(self, name: str) -> str:
|
|
497
|
+
return self._resolve(self._term, self._term_used, name)
|
|
498
|
+
|
|
499
|
+
def predicate(self, name: str) -> str:
|
|
500
|
+
return self._resolve(self._pred, self._pred_used, name)
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def _rewrite_casl_names(node: Node, shim: _CaslIdentifierShim) -> Node:
|
|
504
|
+
"""Rebuild ``node`` with every Variable / (Sorted)Constant / Function /
|
|
505
|
+
Atom-predicate name replaced by ``shim``'s resolution of it.
|
|
506
|
+
|
|
507
|
+
The five term/atom classes are handled explicitly (a name is a plain
|
|
508
|
+
``str`` field, so the generic structural recursion below would copy it
|
|
509
|
+
verbatim rather than sanitise it); every other node — every connective
|
|
510
|
+
and (Sorted)Quantifier — recurses via
|
|
511
|
+
:meth:`~unicode_logic_kit.fol.nodes.Node.map_children`, the kit's shared
|
|
512
|
+
structural-rewrite primitive, which already applies this same function to
|
|
513
|
+
a Quantifier's bound ``variable`` field (a :class:`Variable`, handled by
|
|
514
|
+
the explicit case above) alongside its ``formula`` — so a Quantifier
|
|
515
|
+
needs no special case of its own here. A node type outside
|
|
516
|
+
``fol.casl_export``'s classical fragment (e.g. a raw :class:`Box` reaching
|
|
517
|
+
this function directly, rather than through
|
|
518
|
+
:func:`~unicode_logic_kit.fol.qml.qml_validity_formula` first) is passed
|
|
519
|
+
through unchanged by that same generic recursion — this function performs
|
|
520
|
+
no fragment check of its own; :func:`~unicode_logic_kit.fol.casl_export.to_casl_spec`
|
|
521
|
+
still refuses it, by name, exactly as it always did.
|
|
522
|
+
|
|
523
|
+
An Atom whose predicate is ``=`` or ``≠`` is never renamed (see the section
|
|
524
|
+
comment above :func:`_casl_sanitize_stem`, point (2)): an ``=`` atom is
|
|
525
|
+
CASL's own rigid identity and is kept under its literal name, whatever its
|
|
526
|
+
arity (a non-binary one is refused downstream by
|
|
527
|
+
:func:`~unicode_logic_kit.fol.casl_export.to_casl_spec`, by name, not
|
|
528
|
+
relabelled here); a ``≠`` atom, which CASL has no rendering for at any
|
|
529
|
+
arity and which ``fol.qml`` has already written as ``¬(=)`` before this
|
|
530
|
+
function sees the formula, is refused with :class:`NotImplementedError`
|
|
531
|
+
rather than relabelled onto an unrelated identifier — see that branch's own
|
|
532
|
+
comment.
|
|
533
|
+
"""
|
|
534
|
+
if isinstance(node, Variable):
|
|
535
|
+
return Variable(shim.variable(node.name))
|
|
536
|
+
if isinstance(node, SortedConstant):
|
|
537
|
+
return SortedConstant(shim.term(node.name), node.sort)
|
|
538
|
+
if isinstance(node, Constant):
|
|
539
|
+
return Constant(shim.term(node.name))
|
|
540
|
+
if isinstance(node, Function):
|
|
541
|
+
return Function(shim.term(node.name),
|
|
542
|
+
tuple(_rewrite_casl_names(a, shim) for a in node.args))
|
|
543
|
+
if isinstance(node, Atom):
|
|
544
|
+
args = tuple(_rewrite_casl_names(a, shim) for a in node.args)
|
|
545
|
+
if node.predicate == "=":
|
|
546
|
+
# CASL's own built-in identity: not a declared symbol, nothing to
|
|
547
|
+
# rename. A non-binary '=' (only reachable by hand-building the
|
|
548
|
+
# Node; qml_validity_formula refuses one) is left as it is so that
|
|
549
|
+
# fol.casl_export's exactly-two-terms check refuses it by name.
|
|
550
|
+
return Atom("=", args)
|
|
551
|
+
if node.predicate == "≠":
|
|
552
|
+
# fol.casl_export has no rendering rule for "≠" at ANY arity (CASL
|
|
553
|
+
# has no disequality connective), and fol.qml.qml_validity_formula
|
|
554
|
+
# never leaves one in its output (it writes "≠" as "not (=)"), so a
|
|
555
|
+
# "≠" atom reaches here only in a Node built by hand or parsed from
|
|
556
|
+
# classical text and handed to sanitize_modal_identifiers directly,
|
|
557
|
+
# which is documented as usable standalone. Falling through to the
|
|
558
|
+
# ordinary predicate-renaming path below would silently relabel it
|
|
559
|
+
# onto an arbitrary, unrelated legal identifier ('v_'), losing every
|
|
560
|
+
# trace of "not equal" -- the silent approximation the project's own
|
|
561
|
+
# refuse-loudly rule forbids. Refuse it instead, mirroring how
|
|
562
|
+
# fol.casl_export.to_casl_spec already refuses the same atom when
|
|
563
|
+
# handed it directly ("not a simple CASL identifier"), one layer
|
|
564
|
+
# earlier and with a message that says what to write instead. This
|
|
565
|
+
# sanitiser renames identifiers; it does not rewrite connectives.
|
|
566
|
+
raise NotImplementedError(
|
|
567
|
+
"hets.dol: sanitize_modal_identifiers cannot rename a '≠' atom "
|
|
568
|
+
f"({node.to_unicode_str()}) -- CASL has no native disequality "
|
|
569
|
+
"connective, so it would either become a meaningless renamed "
|
|
570
|
+
"predicate (masking the lost 'not equal' meaning) or crash "
|
|
571
|
+
"fol.casl_export's own identifier check. "
|
|
572
|
+
"unicode_logic_kit.fol.qml.qml_validity_formula never leaves one "
|
|
573
|
+
"in its output (it writes '≠' as 'not (=)' itself), so this "
|
|
574
|
+
"atom did not arrive through it -- write "
|
|
575
|
+
"'Not(Atom(\"=\", [a, b]))' instead of '≠' in the source formula."
|
|
576
|
+
)
|
|
577
|
+
return Atom(shim.predicate(node.predicate), args)
|
|
578
|
+
return node.map_children(lambda c: _rewrite_casl_names(c, shim))
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
def sanitize_modal_identifiers(formula: Node) -> Node:
|
|
582
|
+
"""Return ``formula`` with every not-yet-legal CASL identifier — in
|
|
583
|
+
practice, :mod:`unicode_logic_kit.fol.qml`'s own auto-generated fresh
|
|
584
|
+
variables — renamed to a legal one, INJECTIVELY (two distinct source
|
|
585
|
+
names never collapse onto one output name) and leaving every
|
|
586
|
+
already-legal name untouched. Identity needs no renaming: a ``=`` atom is
|
|
587
|
+
CASL's own native, rigid equality and is kept as it is (see the section
|
|
588
|
+
comment above :func:`_casl_sanitize_stem`, point (2), for why that is also
|
|
589
|
+
what ``fol.qml`` hands over). See :class:`_CaslIdentifierShim` for the exact
|
|
590
|
+
algorithm and :func:`to_dol_library_from_modal`, which calls this
|
|
591
|
+
automatically, for the intended entry point.
|
|
592
|
+
|
|
593
|
+
Exposed publicly (rather than kept as a private helper of
|
|
594
|
+
:func:`to_dol_library_from_modal`) so a caller can inspect the sanitised
|
|
595
|
+
Node itself — e.g. to hand it to
|
|
596
|
+
:func:`~unicode_logic_kit.fol.casl_export.formula_to_casl` /
|
|
597
|
+
:func:`~unicode_logic_kit.fol.casl_export.to_casl_spec` directly instead of
|
|
598
|
+
going through a whole DOL library, or to unit-test the renaming in
|
|
599
|
+
isolation from CASL rendering.
|
|
600
|
+
|
|
601
|
+
A caller who reaches this function directly, bypassing
|
|
602
|
+
:func:`to_dol_library_from_modal` /
|
|
603
|
+
:func:`~unicode_logic_kit.fol.qml.qml_validity_formula`, owns two details the
|
|
604
|
+
translation would otherwise have settled: a ``≠`` atom must already be
|
|
605
|
+
written ``Not(Atom("=", ...))``, because ``fol.qml`` does that rewrite and
|
|
606
|
+
this function does not (it refuses a ``≠`` atom with
|
|
607
|
+
:class:`NotImplementedError` instead of renaming it onto an unrelated
|
|
608
|
+
identifier), and a ``=`` atom must be binary, because a non-binary one is
|
|
609
|
+
passed through untouched for :func:`~unicode_logic_kit.fol.casl_export.to_casl_spec`
|
|
610
|
+
to refuse.
|
|
611
|
+
|
|
612
|
+
Raises:
|
|
613
|
+
NotImplementedError: ``formula`` contains a ``≠`` atom, of any arity
|
|
614
|
+
(CASL has no native rendering for it; ``fol.qml`` writes ``≠`` as
|
|
615
|
+
the negation of ``=`` before a formula reaches this bridge).
|
|
616
|
+
"""
|
|
617
|
+
return _rewrite_casl_names(formula, _CaslIdentifierShim(formula))
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
def to_dol_library_from_modal(
|
|
621
|
+
formula: Node, *,
|
|
622
|
+
mode: str = "constant", frame: str = "K", systems=None, bridges=None,
|
|
623
|
+
temporal_closure: bool = True,
|
|
624
|
+
spec_name: str = "ModalQuery", library_name: str = "ModalLib",
|
|
625
|
+
default_sort: str = "Thing",
|
|
626
|
+
) -> str:
|
|
627
|
+
"""Render one quantified-modal-logic validity query as a complete DOL
|
|
628
|
+
library, ready to upload to Hets — the official, tested composition of
|
|
629
|
+
:func:`unicode_logic_kit.fol.qml.qml_validity_formula` (the modal-to-
|
|
630
|
+
classical-FOL standard translation), :func:`sanitize_modal_identifiers`
|
|
631
|
+
(the identifier fix the section comment above this class explains), and
|
|
632
|
+
THIS module's own :func:`to_dol_library` / :class:`DolSpec` /
|
|
633
|
+
:func:`~unicode_logic_kit.fol.casl_export.to_casl_spec` — no change to any
|
|
634
|
+
of those three's own logic.
|
|
635
|
+
|
|
636
|
+
Args:
|
|
637
|
+
formula: the RAW modal formula (``Box``/``Diamond``/``Knows``/…,
|
|
638
|
+
NOT yet translated) — the same argument
|
|
639
|
+
:func:`~unicode_logic_kit.fol.qml.qml_is_valid` takes.
|
|
640
|
+
mode, frame, systems, bridges, temporal_closure: forwarded verbatim to
|
|
641
|
+
:func:`~unicode_logic_kit.fol.qml.qml_validity_formula` — see that
|
|
642
|
+
function's and :func:`~unicode_logic_kit.fol.qml.qml_is_valid`'s own
|
|
643
|
+
docstrings for what each selects (domain regime, alethic frame
|
|
644
|
+
system, agent-indexed epistemic/doxastic systems, cross-family
|
|
645
|
+
bridges, and the default-on temporal closure axioms).
|
|
646
|
+
spec_name: the single emitted spec's name (one qml validity query is
|
|
647
|
+
one self-contained CASL ``spec`` — there is nothing here to
|
|
648
|
+
structure across several specs the way :func:`to_dol_library`'s
|
|
649
|
+
general ``extends`` chaining supports).
|
|
650
|
+
library_name: the DOL library's own name.
|
|
651
|
+
default_sort: the ONE CASL sort the whole query is declared over —
|
|
652
|
+
:func:`~unicode_logic_kit.fol.qml.qml_translate`'s embedding is
|
|
653
|
+
SINGLE-sorted (worlds and objects share one FO sort, carved apart
|
|
654
|
+
by the ``World``/``Object`` GUARD PREDICATES qml's own axioms
|
|
655
|
+
assert — see ``fol.qml``'s module docstring), so there is no
|
|
656
|
+
richer CASL sort structure to pass through here; this is exactly
|
|
657
|
+
:func:`~unicode_logic_kit.fol.casl_export.to_casl_spec`'s own
|
|
658
|
+
``default_sort`` parameter, forwarded unchanged.
|
|
659
|
+
|
|
660
|
+
The query is emitted as the spec's sole ``%implied`` CONJECTURE (no
|
|
661
|
+
separate axioms — :func:`~unicode_logic_kit.fol.qml.qml_validity_formula`'s
|
|
662
|
+
result is already the one closed implication ``⋀axioms ∧
|
|
663
|
+
⋀typing-facts → …`` in full), so ``client.prove(iri, spec_name)`` is what
|
|
664
|
+
a caller runs against the uploaded library — the same shape
|
|
665
|
+
``tests/test_dol.py``'s own live tests already use.
|
|
666
|
+
|
|
667
|
+
The translation binds world variables (``w``, ``v``, ``w0``, ...) and object variables
|
|
668
|
+
(``x``, ...) of its own, and a user constant, function or predicate of the same
|
|
669
|
+
spelling must not be read as one of them. :func:`~unicode_logic_kit.fol.casl_export.to_casl_spec`
|
|
670
|
+
renames every bound variable that is spelled like a symbol of the spec, so the text
|
|
671
|
+
reads back (through :func:`~unicode_logic_kit.fol.casl_import.parse_casl_spec`) as the
|
|
672
|
+
translation up to the names of those variables, and a constant called ``w`` or ``w0``
|
|
673
|
+
never changes the verdict.
|
|
674
|
+
|
|
675
|
+
An object-language ``=`` atom in ``formula`` arrives from
|
|
676
|
+
:func:`~unicode_logic_kit.fol.qml.qml_validity_formula` as the same binary
|
|
677
|
+
identity over the object terms with no world argument (``≠`` as its
|
|
678
|
+
negation), which is exactly CASL's own rigid, built-in ``=``: it is
|
|
679
|
+
rendered infix, declared nowhere, and read back by
|
|
680
|
+
:func:`~unicode_logic_kit.fol.casl_import.parse_casl_spec` as the identical
|
|
681
|
+
atom. So identity is rigid on this route exactly as it is for
|
|
682
|
+
:func:`~unicode_logic_kit.fol.qml.qml_is_valid` (``a = b → □(a = b)`` is a
|
|
683
|
+
theorem here, in every frame), and the two routes agree on it. See the
|
|
684
|
+
module-level section comment above :func:`_casl_sanitize_stem`, point (2).
|
|
685
|
+
|
|
686
|
+
Raises:
|
|
687
|
+
ValueError: any of :func:`~unicode_logic_kit.fol.qml.qml_axioms`'s /
|
|
688
|
+
:func:`~unicode_logic_kit.fol.qml.qml_translate`'s own refusals
|
|
689
|
+
(an unknown ``mode``/``frame``/``systems``/``bridges`` value, a
|
|
690
|
+
frame requesting a bridge whose partner family the formula never
|
|
691
|
+
mentions, or a ``=``/``≠`` atom that does not have exactly two
|
|
692
|
+
terms), or (propagated, unchanged) any
|
|
693
|
+
:func:`~unicode_logic_kit.fol.casl_export.to_casl_spec` /
|
|
694
|
+
:func:`to_dol_library` refusal for ``spec_name`` / ``library_name``
|
|
695
|
+
/ ``default_sort``.
|
|
696
|
+
NotImplementedError: ``frame`` needs a non-first-order condition
|
|
697
|
+
(Löb/McKinsey/Grz — :func:`~unicode_logic_kit.fol.qml.qml_axioms`
|
|
698
|
+
refuses those by name, pointing at the higher-order routes that
|
|
699
|
+
DO carry them), or ``formula`` uses a construct
|
|
700
|
+
:func:`~unicode_logic_kit.fol.qml.qml_translate` itself does not
|
|
701
|
+
cover (``Until``/``Since``, the ``↓`` binder — see that module's
|
|
702
|
+
own docstring for the exact scope: propositional + first-order
|
|
703
|
+
modal, alethic/temporal/deontic/per-agent
|
|
704
|
+
epistemic-doxastic/PAL).
|
|
705
|
+
"""
|
|
706
|
+
node = qml_validity_formula(formula, mode=mode, frame=frame, systems=systems,
|
|
707
|
+
bridges=bridges, temporal_closure=temporal_closure)
|
|
708
|
+
sanitized = sanitize_modal_identifiers(node)
|
|
709
|
+
specs: "OrderedDict[str, DolSpec]" = OrderedDict()
|
|
710
|
+
specs[spec_name] = DolSpec(axioms=[], conjectures=[sanitized],
|
|
711
|
+
default_sort=default_sort)
|
|
712
|
+
return to_dol_library(library_name, specs)
|