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,105 @@
|
|
|
1
|
+
"""A free variable is a parameter of the problem: one unknown element, the same everywhere.
|
|
2
|
+
|
|
3
|
+
The consequence relation the kit reads is the assignment-wise one of the textbooks:
|
|
4
|
+
``Γ ⊨ φ`` iff every structure AND assignment that satisfies ``Γ`` satisfies ``φ``. A
|
|
5
|
+
variable that is free in a premise or in the conclusion therefore names ONE element,
|
|
6
|
+
the same in every formula of the problem. It is not a universally quantified variable
|
|
7
|
+
of its own formula: ``P(x) ⊢ P(alpha)`` is not valid (universe ``{0, 1}``, ``x`` ↦ 1,
|
|
8
|
+
``alpha`` ↦ 0, ``P`` = ``{1}``), while ``P(x) ⊢ ∃y P(y)`` and ``∀y P(y) ⊢ P(x)`` are.
|
|
9
|
+
|
|
10
|
+
A route that has no free variables of its own (a resolution prover, a model finder, a
|
|
11
|
+
writer for a target that closes every formula) reads this by replacing every free
|
|
12
|
+
variable of the whole problem by a constant, before it does anything else:
|
|
13
|
+
``Γ(x) ⊨ φ(x)`` assignment-wise iff ``Γ(c) ⊨ φ(c)`` for a constant ``c`` that no
|
|
14
|
+
symbol of the problem has. :func:`parameterize` does that, once, for every route that
|
|
15
|
+
needs it. For a problem with no premise the reading coincides with the universal
|
|
16
|
+
closure of the conclusion.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from typing import Dict, Iterable, List, Sequence, Tuple
|
|
20
|
+
|
|
21
|
+
from ._identifiers import symbol_names
|
|
22
|
+
from .nodes import Node, Constant, SortedConstant, Variable, free_variables, substitute
|
|
23
|
+
|
|
24
|
+
__all__ = ["free_parameter_names", "parameterize"]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _free_names(formula: Node) -> frozenset:
|
|
28
|
+
"""The names of the object variables that are free in ``formula``."""
|
|
29
|
+
return frozenset(v.name for v in free_variables(formula) if isinstance(v, Variable))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def free_parameter_names(formulas: Iterable[Node]) -> Tuple[str, ...]:
|
|
33
|
+
"""The names of the variables that are free in at least one of ``formulas``, sorted.
|
|
34
|
+
|
|
35
|
+
A variable is free in a formula when no quantifier, counting quantifier,
|
|
36
|
+
cardinality term or IF-logic binder of that formula binds it; a name that is bound
|
|
37
|
+
in one formula and free in another is in the result. A lambda-bound variable is not
|
|
38
|
+
an object variable and is never in it.
|
|
39
|
+
"""
|
|
40
|
+
names: set = set()
|
|
41
|
+
for formula in formulas:
|
|
42
|
+
names |= _free_names(formula)
|
|
43
|
+
return tuple(sorted(names))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def parameterize(formulas: Sequence[Node], *, avoid: Iterable[str] = (),
|
|
47
|
+
after_variables: bool = False) -> Tuple[List[Node], Dict[str, Constant]]:
|
|
48
|
+
"""Replace every free variable of ``formulas`` by a constant, the same one in all of them.
|
|
49
|
+
|
|
50
|
+
Returns ``(closed, parameters)``: the formulas with every free occurrence of a
|
|
51
|
+
variable replaced, and a mapping from each variable's name to the constant that
|
|
52
|
+
stands for it. A variable that is bound inside a formula is left alone there (the
|
|
53
|
+
substitution is capture-avoiding), and a name that is free in one formula and bound
|
|
54
|
+
in another is replaced only where it is free. Formulas without a free variable
|
|
55
|
+
come back as they are.
|
|
56
|
+
|
|
57
|
+
By default the constants are minted (``_p0``, ``_p1``, …) and none of them has the
|
|
58
|
+
spelling of any name that ``formulas`` carry -- a predicate, function, constant,
|
|
59
|
+
sort, variable or nominal -- nor of any name in ``avoid``, so no constant of the
|
|
60
|
+
problem is captured by a parameter. Pass ``avoid`` for names of a vocabulary the
|
|
61
|
+
problem will be read together with.
|
|
62
|
+
|
|
63
|
+
With ``after_variables=True`` the constant carries the variable's own name, so that
|
|
64
|
+
a structure found for the closed formulas reports the parameter under the name the
|
|
65
|
+
caller wrote. A parameter that would then have the name of a constant of the
|
|
66
|
+
problem (``Constant('x')`` next to a free variable ``x``: the Unicode text tells the
|
|
67
|
+
two apart, ``P(x) ∧ Q('x')``, but a structure holds one entry per name) cannot be
|
|
68
|
+
told from that constant in a table keyed by name, so it is refused.
|
|
69
|
+
|
|
70
|
+
Raises:
|
|
71
|
+
NotImplementedError: ``after_variables`` is true and a free variable has the
|
|
72
|
+
name of a constant (plain or sorted) of ``formulas``.
|
|
73
|
+
"""
|
|
74
|
+
formulas = list(formulas)
|
|
75
|
+
free_per_formula = [_free_names(formula) for formula in formulas]
|
|
76
|
+
names = sorted(set().union(*free_per_formula)) if free_per_formula else []
|
|
77
|
+
if not names:
|
|
78
|
+
return formulas, {}
|
|
79
|
+
parameters: Dict[str, Constant] = {}
|
|
80
|
+
if after_variables:
|
|
81
|
+
constants = {node.name for formula in formulas for node in formula.walk()
|
|
82
|
+
if isinstance(node, (Constant, SortedConstant))}
|
|
83
|
+
for name in names:
|
|
84
|
+
if name in constants:
|
|
85
|
+
raise NotImplementedError(
|
|
86
|
+
f"a free variable {name!r} and a constant {name!r} are in one problem: "
|
|
87
|
+
"a structure holds one entry per name, so the parameter that stands "
|
|
88
|
+
"for the variable could not be told from the constant. Rename one of "
|
|
89
|
+
"them.")
|
|
90
|
+
parameters[name] = Constant(name)
|
|
91
|
+
else:
|
|
92
|
+
taken = set(symbol_names(*formulas)) | set(avoid)
|
|
93
|
+
index = 0
|
|
94
|
+
for name in names:
|
|
95
|
+
while f"_p{index}" in taken:
|
|
96
|
+
index += 1
|
|
97
|
+
parameters[name] = Constant(f"_p{index}")
|
|
98
|
+
taken.add(f"_p{index}")
|
|
99
|
+
index += 1
|
|
100
|
+
closed = []
|
|
101
|
+
for formula, free in zip(formulas, free_per_formula):
|
|
102
|
+
for name in sorted(free):
|
|
103
|
+
formula = substitute(formula, Variable(name), parameters[name])
|
|
104
|
+
closed.append(formula)
|
|
105
|
+
return closed, parameters
|
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
"""Third-order AST: a predicate standing in ARGUMENT position, and its typing.
|
|
2
|
+
|
|
3
|
+
Second-order syntax lets a predicate variable be *bound* (``∀P φ``); it still
|
|
4
|
+
only ever appears as the HEAD of an application, ``P(x)``. Third-order syntax
|
|
5
|
+
adds the one thing that is genuinely a level up: a predicate whose ARGUMENT is
|
|
6
|
+
itself a predicate — ``Positive(G)``, ``Essence(G, x)``,
|
|
7
|
+
``Positive(λx. ¬G(x))``. No amount of extra quantification reaches that; it is a
|
|
8
|
+
change to the argument layer, which is why it needs its own node
|
|
9
|
+
(:class:`PredicateTerm`), its own grammar modes (``third_order`` /
|
|
10
|
+
``third_order_modal``, see ``_MODE_ATOM_ARGS`` in ``_fol_nodes.py``) and its own
|
|
11
|
+
typing pass (:func:`analyse_signatures`).
|
|
12
|
+
|
|
13
|
+
**The node.** ``PredicateTerm(name)`` is a leaf that occupies an argument slot
|
|
14
|
+
and denotes the property/relation the name stands for — not a truth value. It is
|
|
15
|
+
deliberately *not* an ``Atom`` with zero arguments: ``P`` as a nullary atom is a
|
|
16
|
+
proposition, ``PredicateTerm("P")`` is the predicate itself, and conflating them
|
|
17
|
+
is exactly the type error the third order exists to make visible. Its ``name``
|
|
18
|
+
field is spelled like ``Variable.name`` / ``Constant.name`` / ``LambdaVar.name``
|
|
19
|
+
so the shared term renderers reach it by the same path.
|
|
20
|
+
|
|
21
|
+
Every first-order back-end refuses it, for the same reason
|
|
22
|
+
``SecondOrderQuantifier`` does: a property argument has no first-order or
|
|
23
|
+
SMT-LIB reading. The HOL exporters are where it goes
|
|
24
|
+
(:mod:`unicode_logic_kit.hol.thirdorder`,
|
|
25
|
+
:mod:`unicode_logic_kit.hol.ho_modal`).
|
|
26
|
+
|
|
27
|
+
**The typing pass.** A predicate's argument slots are not annotated in the
|
|
28
|
+
surface syntax, so what each slot holds — an individual, or a property of arity
|
|
29
|
+
k — is *inferred*, and the inference has to run over a whole theory rather than
|
|
30
|
+
one formula: ``Positive(Phi)`` alone says nothing about Phi's arity, while
|
|
31
|
+
``Positive(G)`` together with ``G(x)`` elsewhere in the same axiom set fixes it
|
|
32
|
+
at 1. :func:`analyse_signatures` takes any number of formulas and returns one
|
|
33
|
+
:class:`Signatures` for all of them: it collects the constraints (an application
|
|
34
|
+
fixes its head's arity; a λ-argument fixes its slot's arity by binder depth; a
|
|
35
|
+
PredicateTerm links its own arity to its slot's) and closes them under
|
|
36
|
+
propagation until nothing moves.
|
|
37
|
+
|
|
38
|
+
Three ways that can fail, all raised rather than papered over: a predicate applied
|
|
39
|
+
at two different arities (``ConflictingArityError``, shared with the second-order
|
|
40
|
+
inference), a slot used once for an individual and once for a property
|
|
41
|
+
(:class:`MixedSlotError`), and a slot that would have to hold a property of
|
|
42
|
+
PROPERTIES (:class:`NestedPropertySlotError`): ``Meta(Pos) ∧ Pos(G)`` makes ``Pos`` a
|
|
43
|
+
predicate of properties and puts it in a slot of ``Meta``, a fourth-order typing that
|
|
44
|
+
a slot type ``("p", k)`` (a relation on individuals) cannot state.
|
|
45
|
+
|
|
46
|
+
One way it can be *underdetermined*: a slot no evidence ever reaches — e.g.
|
|
47
|
+
``∀Phi (Positive(Phi) → □Positive(Phi))`` read entirely on its own, where Phi is
|
|
48
|
+
never applied and shares its slot with nothing that is. Such a slot defaults to
|
|
49
|
+
a property of arity **1**, because argument position is what makes it a property
|
|
50
|
+
slot at all and 1 is the only reading on which the formula says what it is
|
|
51
|
+
plainly meant to say; arity 0 would silently retype it as a predicate over
|
|
52
|
+
PROPOSITIONS. The default is recorded in :attr:`Signatures.defaulted` so a
|
|
53
|
+
caller that would rather refuse than guess can see exactly which slots were
|
|
54
|
+
guessed — the HOL exporters print them as a comment in the emitted theory.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
from dataclasses import dataclass
|
|
58
|
+
from typing import Dict, FrozenSet, List, Sequence, Set, Tuple
|
|
59
|
+
|
|
60
|
+
from ._fol_nodes import (
|
|
61
|
+
Node, Z3Env, NODE_CLASSES, Atom, PARSER_OPS, ParserOp, parser_ops_for_mode,
|
|
62
|
+
)
|
|
63
|
+
from ._so_nodes import (
|
|
64
|
+
SecondOrderQuantifier, ConflictingArityError, _infer_so_arity,
|
|
65
|
+
)
|
|
66
|
+
from ._truth_constants import truth_value
|
|
67
|
+
from .naming import ParsingError
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
_NO_EXPORT = (
|
|
71
|
+
"A predicate in argument position (third order) has no first-order or "
|
|
72
|
+
"SMT-LIB reading: its argument denotes a PROPERTY, not an individual. Use "
|
|
73
|
+
"the HOL exporters (hol.thirdorder / hol.ho_modal) instead."
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@dataclass(frozen=True)
|
|
78
|
+
class PredicateTerm(Node):
|
|
79
|
+
"""A predicate name occupying an argument slot — the third-order term.
|
|
80
|
+
|
|
81
|
+
``name`` is the predicate's name as written. The node denotes the property
|
|
82
|
+
(or relation) itself, so it is a TERM, not a formula: ``Positive(G)``
|
|
83
|
+
parses to ``Atom("Positive", [PredicateTerm("G")])``, whereas ``Positive``
|
|
84
|
+
applied to the *proposition* G would be ``Atom("Positive", [Atom("G", [])])``.
|
|
85
|
+
|
|
86
|
+
The node carries no arity of its own. What arity the property has is a
|
|
87
|
+
property of the whole theory, not of this occurrence, and is answered by
|
|
88
|
+
:func:`analyse_signatures`.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
name: str
|
|
92
|
+
|
|
93
|
+
def _tree_parts(self):
|
|
94
|
+
"""Return the ``PredicateTerm: G`` label and no children (it is a leaf)."""
|
|
95
|
+
return f"PredicateTerm: {self.name}", []
|
|
96
|
+
|
|
97
|
+
def to_dict(self):
|
|
98
|
+
"""Serialise to dict with type tag and predicate name."""
|
|
99
|
+
return {"_type": "PredicateTerm", "name": self.name}
|
|
100
|
+
|
|
101
|
+
@staticmethod
|
|
102
|
+
def from_dict(d):
|
|
103
|
+
"""Deserialise a PredicateTerm from a dict produced by to_dict."""
|
|
104
|
+
return PredicateTerm(d["name"])
|
|
105
|
+
|
|
106
|
+
def to_z3(self, env: Z3Env = None):
|
|
107
|
+
"""Reject Z3 export: a property argument is not SMT-expressible."""
|
|
108
|
+
raise NotImplementedError(_NO_EXPORT)
|
|
109
|
+
|
|
110
|
+
def to_prover9(self) -> str:
|
|
111
|
+
"""Reject Prover9 export: a property argument is not first-order."""
|
|
112
|
+
raise NotImplementedError(_NO_EXPORT)
|
|
113
|
+
|
|
114
|
+
def to_tptp(self) -> str:
|
|
115
|
+
"""Reject TPTP (FOF) export: a property argument is not first-order."""
|
|
116
|
+
raise NotImplementedError(_NO_EXPORT)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
NODE_CLASSES.update({"PredicateTerm": PredicateTerm})
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# =========================
|
|
123
|
+
# Signature inference
|
|
124
|
+
# =========================
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class MixedSlotError(ParsingError):
|
|
128
|
+
"""Raised when one argument slot is used for both an individual and a property.
|
|
129
|
+
|
|
130
|
+
``Loves(x, y)`` and ``Loves(x, G)`` cannot both be right: the second slot
|
|
131
|
+
would have to hold an individual and a property at once. Subclasses
|
|
132
|
+
ParsingError so a caller catching the parser's error type also catches this.
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
def __init__(self, predicate: str, position: int):
|
|
136
|
+
message = (
|
|
137
|
+
f"TYPE_ERROR: argument slot {position} of '{predicate}' is used "
|
|
138
|
+
f"both for an individual and for a predicate; a slot holds one or "
|
|
139
|
+
f"the other, not both."
|
|
140
|
+
)
|
|
141
|
+
self.args = (message,)
|
|
142
|
+
|
|
143
|
+
def __str__(self):
|
|
144
|
+
return self.args[0]
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class NestedPropertySlotError(ParsingError):
|
|
148
|
+
"""Raised when a property slot would hold a predicate that itself takes a property.
|
|
149
|
+
|
|
150
|
+
A slot that holds a predicate is typed as a property of INDIVIDUALS, of the arity of that
|
|
151
|
+
predicate. ``Meta(Pos)`` together with ``Pos(G)`` breaks that: ``Pos`` takes a property,
|
|
152
|
+
so it is a predicate of properties, and what ``Meta`` takes is then a property of
|
|
153
|
+
properties (a fourth-order typing). The typing cannot state it, so it is refused instead of
|
|
154
|
+
being typed as if ``Pos`` were a property of individuals. Subclasses ParsingError, like
|
|
155
|
+
:class:`MixedSlotError`.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
def __init__(self, host: str, host_position: int, occupant: str, occupant_position: int):
|
|
159
|
+
message = (
|
|
160
|
+
f"TYPE_ERROR: '{host}' takes the predicate '{occupant}' as a property in "
|
|
161
|
+
f"argument slot {host_position}, but '{occupant}' itself takes a property in its "
|
|
162
|
+
f"argument slot {occupant_position}: '{host}' would be a predicate of predicates "
|
|
163
|
+
f"of properties (fourth order or higher). A property slot holds a relation on "
|
|
164
|
+
f"individuals only, so this is refused rather than typed as if '{occupant}' were "
|
|
165
|
+
f"a property of individuals."
|
|
166
|
+
)
|
|
167
|
+
self.args = (message,)
|
|
168
|
+
|
|
169
|
+
def __str__(self):
|
|
170
|
+
return self.args[0]
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
#: What one argument slot holds. ``"i"`` is an individual; ``("p", k)`` is a
|
|
174
|
+
#: property/relation of arity ``k``.
|
|
175
|
+
INDIVIDUAL = "i"
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
@dataclass(frozen=True)
|
|
179
|
+
class Signatures:
|
|
180
|
+
"""The result of :func:`analyse_signatures` over one or more formulas.
|
|
181
|
+
|
|
182
|
+
``arity`` maps every predicate NAME that is applied (or reached by
|
|
183
|
+
propagation) to its arity. ``slots`` maps a predicate name to its argument
|
|
184
|
+
signature — a tuple with one entry per position, each either
|
|
185
|
+
:data:`INDIVIDUAL` or ``("p", k)`` for a property of arity ``k``.
|
|
186
|
+
``defaulted`` names the ``(predicate, position)`` pairs whose property arity
|
|
187
|
+
no evidence fixed and which therefore defaulted to 1.
|
|
188
|
+
"""
|
|
189
|
+
|
|
190
|
+
arity: Dict[str, int]
|
|
191
|
+
slots: Dict[str, Tuple[object, ...]]
|
|
192
|
+
defaulted: FrozenSet[Tuple[str, int]]
|
|
193
|
+
|
|
194
|
+
def is_third_order(self) -> bool:
|
|
195
|
+
"""True iff some predicate takes a property argument (i.e. the theory really is third order)."""
|
|
196
|
+
return any(s != INDIVIDUAL for sig in self.slots.values() for s in sig)
|
|
197
|
+
|
|
198
|
+
def property_slots(self) -> List[Tuple[str, int, int]]:
|
|
199
|
+
"""Return ``(predicate, position, arity)`` for every property-holding slot, sorted."""
|
|
200
|
+
out = []
|
|
201
|
+
for pred in sorted(self.slots):
|
|
202
|
+
for pos, kind in enumerate(self.slots[pred]):
|
|
203
|
+
if kind != INDIVIDUAL:
|
|
204
|
+
out.append((pred, pos, kind[1]))
|
|
205
|
+
return out
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _lambda_depth(node: Node) -> int:
|
|
209
|
+
"""Return how many leading λ binders ``node`` has (0 if it is not a Lambda).
|
|
210
|
+
|
|
211
|
+
``λx. λy. φ`` is a binary relation, so its depth IS the arity of the
|
|
212
|
+
property it denotes. Imported lazily: Lambda lives in _msfl_nodes, which
|
|
213
|
+
imports this module's dependencies rather than the other way round.
|
|
214
|
+
"""
|
|
215
|
+
from ._msfl_nodes import Lambda
|
|
216
|
+
depth = 0
|
|
217
|
+
while isinstance(node, Lambda):
|
|
218
|
+
depth += 1
|
|
219
|
+
node = node.body
|
|
220
|
+
return depth
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def analyse_signatures(formulas: Sequence[Node]) -> Signatures:
|
|
224
|
+
"""Infer every predicate's arity and argument signature across ``formulas``.
|
|
225
|
+
|
|
226
|
+
Runs over the formulas TOGETHER, because that is the scope on which the
|
|
227
|
+
answer is determined: ``Positive(Phi)`` in one axiom and ``Phi(x)`` in
|
|
228
|
+
another jointly fix Phi's arity at 1, and neither does so alone.
|
|
229
|
+
|
|
230
|
+
Raises ConflictingArityError if a predicate is applied at two arities,
|
|
231
|
+
:class:`MixedSlotError` if one slot holds an individual in one place and a
|
|
232
|
+
property in another, and :class:`NestedPropertySlotError` if a predicate that
|
|
233
|
+
stands in a property slot itself takes a property (a property of properties,
|
|
234
|
+
which the typing cannot state). A property slot that no evidence reaches defaults to
|
|
235
|
+
arity 1 and is listed in the result's ``defaulted`` — see this module's
|
|
236
|
+
docstring for why 1 and not 0.
|
|
237
|
+
"""
|
|
238
|
+
app_arity: Dict[str, Set[int]] = {}
|
|
239
|
+
# slot_kind[(pred, pos)] is INDIVIDUAL, or ("p", None) until its arity is known.
|
|
240
|
+
slot_kind: Dict[Tuple[str, int], object] = {}
|
|
241
|
+
# Which predicate NAMES occupy which slots (a slot may be occupied by several).
|
|
242
|
+
slot_occupants: Dict[Tuple[str, int], Set[str]] = {}
|
|
243
|
+
# Property arities pinned directly by a λ-argument's binder depth.
|
|
244
|
+
slot_lambda_arity: Dict[Tuple[str, int], Set[int]] = {}
|
|
245
|
+
|
|
246
|
+
def note_slot(pred: str, pos: int, kind: object) -> None:
|
|
247
|
+
prev = slot_kind.get((pred, pos))
|
|
248
|
+
if prev is None:
|
|
249
|
+
slot_kind[(pred, pos)] = kind
|
|
250
|
+
return
|
|
251
|
+
prev_is_prop = prev != INDIVIDUAL
|
|
252
|
+
now_is_prop = kind != INDIVIDUAL
|
|
253
|
+
if prev_is_prop != now_is_prop:
|
|
254
|
+
raise MixedSlotError(pred, pos)
|
|
255
|
+
|
|
256
|
+
def visit(node: Node) -> None:
|
|
257
|
+
if isinstance(node, Atom) and truth_value(node) is None:
|
|
258
|
+
# (`$true` / `$false` are constants, not predicates: nothing to type.)
|
|
259
|
+
app_arity.setdefault(node.predicate, set()).add(len(node.args))
|
|
260
|
+
for pos, arg in enumerate(node.args):
|
|
261
|
+
if isinstance(arg, PredicateTerm):
|
|
262
|
+
note_slot(node.predicate, pos, ("p", None))
|
|
263
|
+
slot_occupants.setdefault((node.predicate, pos), set()).add(arg.name)
|
|
264
|
+
elif _lambda_depth(arg) > 0:
|
|
265
|
+
note_slot(node.predicate, pos, ("p", None))
|
|
266
|
+
slot_lambda_arity.setdefault((node.predicate, pos), set()).add(
|
|
267
|
+
_lambda_depth(arg))
|
|
268
|
+
else:
|
|
269
|
+
note_slot(node.predicate, pos, INDIVIDUAL)
|
|
270
|
+
for child in node._child_nodes():
|
|
271
|
+
visit(child)
|
|
272
|
+
|
|
273
|
+
for formula in formulas:
|
|
274
|
+
visit(formula)
|
|
275
|
+
|
|
276
|
+
# An application fixes its head's arity outright; two different arities for
|
|
277
|
+
# one name is the same error the second-order inference already raises.
|
|
278
|
+
arity: Dict[str, int] = {}
|
|
279
|
+
for pred, arities in app_arity.items():
|
|
280
|
+
if len(arities) > 1:
|
|
281
|
+
raise ConflictingArityError(pred, sorted(arities))
|
|
282
|
+
arity[pred] = next(iter(arities))
|
|
283
|
+
|
|
284
|
+
# A λ-argument pins its slot's arity directly.
|
|
285
|
+
slot_arity: Dict[Tuple[str, int], int] = {}
|
|
286
|
+
for slot, depths in slot_lambda_arity.items():
|
|
287
|
+
if len(depths) > 1:
|
|
288
|
+
raise ConflictingArityError(f"{slot[0]}[{slot[1]}]", sorted(depths))
|
|
289
|
+
slot_arity[slot] = next(iter(depths))
|
|
290
|
+
|
|
291
|
+
# A predicate that sits in a property slot is typed as a property of INDIVIDUALS (a slot
|
|
292
|
+
# type is ("p", k), a relation on individuals). One that takes a property itself is a
|
|
293
|
+
# predicate of properties, and the slot would have to hold a property of properties.
|
|
294
|
+
property_position: Dict[str, int] = {}
|
|
295
|
+
for (name, position), kind in sorted(slot_kind.items()):
|
|
296
|
+
if kind != INDIVIDUAL:
|
|
297
|
+
property_position.setdefault(name, position)
|
|
298
|
+
for (host, host_position), occupants in sorted(slot_occupants.items()):
|
|
299
|
+
for occupant in sorted(occupants):
|
|
300
|
+
if occupant in property_position:
|
|
301
|
+
raise NestedPropertySlotError(host, host_position, occupant,
|
|
302
|
+
property_position[occupant])
|
|
303
|
+
|
|
304
|
+
# Close under propagation: a known predicate arity fixes every slot it sits
|
|
305
|
+
# in, and a known slot arity fixes every predicate sitting in it.
|
|
306
|
+
changed = True
|
|
307
|
+
while changed:
|
|
308
|
+
changed = False
|
|
309
|
+
for slot, occupants in slot_occupants.items():
|
|
310
|
+
known = {arity[p] for p in occupants if p in arity}
|
|
311
|
+
if slot in slot_arity:
|
|
312
|
+
known.add(slot_arity[slot])
|
|
313
|
+
if len(known) > 1:
|
|
314
|
+
raise ConflictingArityError(f"{slot[0]}[{slot[1]}]", sorted(known))
|
|
315
|
+
if not known:
|
|
316
|
+
continue
|
|
317
|
+
value = next(iter(known))
|
|
318
|
+
if slot_arity.get(slot) != value:
|
|
319
|
+
slot_arity[slot] = value
|
|
320
|
+
changed = True
|
|
321
|
+
for p in occupants:
|
|
322
|
+
if arity.get(p) != value:
|
|
323
|
+
if p in arity:
|
|
324
|
+
raise ConflictingArityError(p, sorted({arity[p], value}))
|
|
325
|
+
arity[p] = value
|
|
326
|
+
changed = True
|
|
327
|
+
|
|
328
|
+
# Whatever propagation never reached is a property slot with no evidence.
|
|
329
|
+
defaulted: Set[Tuple[str, int]] = set()
|
|
330
|
+
for slot, kind in slot_kind.items():
|
|
331
|
+
if kind == INDIVIDUAL:
|
|
332
|
+
continue
|
|
333
|
+
if slot not in slot_arity:
|
|
334
|
+
slot_arity[slot] = 1
|
|
335
|
+
defaulted.add(slot)
|
|
336
|
+
for p in slot_occupants.get(slot, ()):
|
|
337
|
+
arity.setdefault(p, 1)
|
|
338
|
+
|
|
339
|
+
slots: Dict[str, Tuple[object, ...]] = {}
|
|
340
|
+
for pred, n in arity.items():
|
|
341
|
+
sig = []
|
|
342
|
+
for pos in range(n):
|
|
343
|
+
kind = slot_kind.get((pred, pos), INDIVIDUAL)
|
|
344
|
+
sig.append(INDIVIDUAL if kind == INDIVIDUAL
|
|
345
|
+
else ("p", slot_arity[(pred, pos)]))
|
|
346
|
+
slots[pred] = tuple(sig)
|
|
347
|
+
|
|
348
|
+
return Signatures(arity, slots, frozenset(defaulted))
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def has_property_argument(node: Node) -> bool:
|
|
352
|
+
"""True iff some predicate application in ``node`` has a property argument.
|
|
353
|
+
|
|
354
|
+
That is the syntactic marker of third order — a PredicateTerm or a
|
|
355
|
+
λ-abstraction sitting in an argument slot. Used to keep the second-order
|
|
356
|
+
path exactly as it was: a formula without one is answered by the cheaper
|
|
357
|
+
application-scan, unchanged, and pays nothing for the richer inference.
|
|
358
|
+
"""
|
|
359
|
+
if isinstance(node, Atom):
|
|
360
|
+
for arg in node.args:
|
|
361
|
+
if isinstance(arg, PredicateTerm) or _lambda_depth(arg) > 0:
|
|
362
|
+
return True
|
|
363
|
+
return any(has_property_argument(child) for child in node._child_nodes())
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def infer_bound_arity(body: Node, predname: str) -> int:
|
|
367
|
+
"""Infer a third-order-bound predicate variable's arity from ``body``.
|
|
368
|
+
|
|
369
|
+
The third-order counterpart of ``_so_nodes._infer_so_arity``: it answers the
|
|
370
|
+
same question, but has one more source of evidence to consult, because in a
|
|
371
|
+
third-order formula ``predname`` may never be applied at all and still be a
|
|
372
|
+
property — ``∀Phi (Positive(Phi) → □Positive(Phi))``. Delegates to
|
|
373
|
+
:func:`analyse_signatures`, whose propagation covers both cases; falls back
|
|
374
|
+
to 0 for a name that genuinely occurs nowhere, matching the second-order
|
|
375
|
+
reading of an unapplied predicate variable as propositional.
|
|
376
|
+
|
|
377
|
+
A body with no property argument at all IS a second-order body, and is sent
|
|
378
|
+
straight to ``_infer_so_arity`` — same answer, same cost, and the
|
|
379
|
+
second-order mode's behaviour is then unchanged by construction rather than
|
|
380
|
+
by inspection.
|
|
381
|
+
|
|
382
|
+
Shadowing follows ``_infer_so_arity``: an inner binder over the SAME name
|
|
383
|
+
starts a fresh binding, so its scope is cut out before the analysis.
|
|
384
|
+
"""
|
|
385
|
+
if not has_property_argument(body):
|
|
386
|
+
return _infer_so_arity(body, predname)
|
|
387
|
+
pruned = _prune_shadowed(body, predname)
|
|
388
|
+
signatures = analyse_signatures([pruned])
|
|
389
|
+
return signatures.arity.get(predname, 0)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def _prune_shadowed(node: Node, predname: str) -> Node:
|
|
393
|
+
"""Replace every subformula that rebinds ``predname`` with a constant-true stand-in.
|
|
394
|
+
|
|
395
|
+
The inner binder's scope must not contribute evidence about the OUTER
|
|
396
|
+
binding, so it is cut away before the signature analysis runs. The stand-in
|
|
397
|
+
is a zero-argument atom under a reserved name that no source formula can
|
|
398
|
+
spell (it contains a space), so it cannot collide with a real predicate.
|
|
399
|
+
"""
|
|
400
|
+
if isinstance(node, SecondOrderQuantifier) and node.predicate == predname:
|
|
401
|
+
return Atom("shadowed scope", [])
|
|
402
|
+
return node.map_children(lambda child: _prune_shadowed(child, predname))
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
# =========================
|
|
406
|
+
# Grammar-mode assembly
|
|
407
|
+
# =========================
|
|
408
|
+
#
|
|
409
|
+
# The two third-order modes are not a new operator set — they are an EXISTING
|
|
410
|
+
# operator set over the widened argument layer. ``third_order`` is the
|
|
411
|
+
# second-order mode's operators; ``third_order_modal`` is those plus the whole
|
|
412
|
+
# modal family. Rather than re-registering ~40 ParserOps (which would drift the
|
|
413
|
+
# moment an operator is added to one mode and not the other), each is CLONED
|
|
414
|
+
# from its sources, so the third-order modes accept exactly what their base
|
|
415
|
+
# modes accept, by construction. ``tests/test_third_order.py`` pins that
|
|
416
|
+
# correspondence.
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def _clone_parser_ops(target: str, sources: Sequence[str]) -> List[ParserOp]:
|
|
420
|
+
"""Register into ``target`` a copy of every ParserOp of the ``sources``, deduped.
|
|
421
|
+
|
|
422
|
+
Two source modes overlap (the classical connectives and the first-order
|
|
423
|
+
quantifier are registered for both ``modal`` and ``second_order``); an
|
|
424
|
+
operator is taken from whichever source names it first and skipped
|
|
425
|
+
afterwards, keyed on the parse-level identity that matters —
|
|
426
|
+
``(level, rule_alias, grammar, only_name)``. Appends to PARSER_OPS and
|
|
427
|
+
returns what was appended.
|
|
428
|
+
"""
|
|
429
|
+
seen = set()
|
|
430
|
+
added: List[ParserOp] = []
|
|
431
|
+
for source in sources:
|
|
432
|
+
for op in parser_ops_for_mode(source):
|
|
433
|
+
key = (op.level, op.rule_alias, op.grammar, op.only_name)
|
|
434
|
+
if key in seen:
|
|
435
|
+
continue
|
|
436
|
+
seen.add(key)
|
|
437
|
+
clone = ParserOp(target, op.level, op.terminal_name, op.terminal_def,
|
|
438
|
+
op.grammar, op.rule_alias, op.transform,
|
|
439
|
+
op.node_class, op.only_name)
|
|
440
|
+
PARSER_OPS.append(clone)
|
|
441
|
+
added.append(clone)
|
|
442
|
+
return added
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
# NOT called here. Cloning has to happen after EVERY module that registers an
|
|
446
|
+
# operator for a source mode has run -- _hybrid_nodes registers @i / nominals
|
|
447
|
+
# for "modal" and is imported after this module's own dependencies -- so the
|
|
448
|
+
# call site is nodes.py, which is the one place that controls that order.
|