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,520 @@
|
|
|
1
|
+
"""Command-line interface for unicode-logic-kit.
|
|
2
|
+
|
|
3
|
+
Two ways to run this module, dispatched purely on ``argv[0]``:
|
|
4
|
+
|
|
5
|
+
**Legacy mode** (unchanged since Tier 0) — parse a single formula and render
|
|
6
|
+
it in one output format::
|
|
7
|
+
|
|
8
|
+
python -m unicode_logic_kit "∀x P(x)" --to latex
|
|
9
|
+
|
|
10
|
+
The ``--mode`` flag selects the parser dialect (which maps onto the
|
|
11
|
+
constructor flags of :class:`MSFLParser`, table-driven via ``_MODE_KWARGS``);
|
|
12
|
+
``--to`` selects the rendering applied to the parsed AST. A formula that does
|
|
13
|
+
not parse prints its message to stderr and exits 1; a formula that parses but
|
|
14
|
+
that the chosen rendering REFUSES (``NotImplementedError`` / ``ValueError`` from
|
|
15
|
+
the renderer: a modal operator has no TPTP form, two distinct names would be
|
|
16
|
+
written as one TPTP word, a constant Prover9 would read as a variable, a
|
|
17
|
+
non-finite number) is reported exactly like the same failure of a subcommand:
|
|
18
|
+
one message on stderr, exit 3, no traceback.
|
|
19
|
+
|
|
20
|
+
**Subcommand mode** — a 1:1 mirror of :mod:`unicode_logic_kit.api`'s verbs onto
|
|
21
|
+
the command line, for every ``argv[0]`` in
|
|
22
|
+
``{check, equiv, prove, countermodel, repair, translate}``::
|
|
23
|
+
|
|
24
|
+
python -m unicode_logic_kit check "∀x P(x)"
|
|
25
|
+
python -m unicode_logic_kit equiv "P → Q" "¬P ∨ Q"
|
|
26
|
+
python -m unicode_logic_kit prove "Q" --premise "P" --premise "P → Q"
|
|
27
|
+
python -m unicode_logic_kit countermodel "∀x P(x)"
|
|
28
|
+
python -m unicode_logic_kit repair "∀x (P(x)"
|
|
29
|
+
python -m unicode_logic_kit translate "□P" --from modal --to-logic fol
|
|
30
|
+
|
|
31
|
+
Dispatch rule: if ``argv`` is non-empty and ``argv[0]`` names one of the six
|
|
32
|
+
subcommands above, the rest of ``argv`` is parsed by that subcommand's own
|
|
33
|
+
parser; otherwise ``argv`` goes to the legacy single-formula parser
|
|
34
|
+
unchanged. Because the legacy grammar's first positional is the formula
|
|
35
|
+
itself, a formula that happens to be spelled exactly ``"check"`` (etc.) would
|
|
36
|
+
be misrouted — an acceptable, documented edge case shared with any CLI that
|
|
37
|
+
grows subcommands after a positional-argument v1.
|
|
38
|
+
|
|
39
|
+
Every subcommand accepts three common flags: ``--dialect`` (the
|
|
40
|
+
:func:`~unicode_logic_kit.api.parse_any` dialect hint; omitted means
|
|
41
|
+
auto-detect), ``--json`` (machine-readable ``to_dict()`` JSON instead of the
|
|
42
|
+
default human-readable summary), and ``--timeout`` (milliseconds, forwarded
|
|
43
|
+
to whichever backend/solver call the subcommand makes; accepted uniformly
|
|
44
|
+
even by subcommands with nothing to forward it to).
|
|
45
|
+
|
|
46
|
+
Error culture: a malformed FORMULA argument to ``check``/``repair`` is not an
|
|
47
|
+
error — that is the diagnosis those two verbs exist to report (``ok=False``,
|
|
48
|
+
exit 1). For every other subcommand, a formula that :func:`api.parse_any`
|
|
49
|
+
cannot parse, an unknown/unavailable backend
|
|
50
|
+
(:class:`~unicode_logic_kit.atp.protocol.BackendUnavailable`), an unsupported
|
|
51
|
+
translation fragment (``NotImplementedError``), or a bad ``--signature``
|
|
52
|
+
JSON file all print one clean message to stderr and exit 3 — never a Python
|
|
53
|
+
traceback. The legacy path treats a rendering that refuses its formula the same
|
|
54
|
+
way (see above).
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
import argparse
|
|
58
|
+
import json
|
|
59
|
+
import sys
|
|
60
|
+
|
|
61
|
+
from .fol.msflparser import MSFLParser
|
|
62
|
+
from .fol.naming import NamingError, ParsingError
|
|
63
|
+
from . import api
|
|
64
|
+
from .atp.protocol import BackendUnavailable
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# Map each --mode value to the MSFLParser constructor kwargs it selects. Every
|
|
68
|
+
# entry here must be a legal (non-conflicting) combination on its own — see
|
|
69
|
+
# MSFLParser's own mutual-exclusivity rules (module docstring / __init__).
|
|
70
|
+
# Reused (read-only) by the subcommand dispatcher below: its keys double as
|
|
71
|
+
# the set of dialect-hint names that :func:`~unicode_logic_kit.api.parse_any`
|
|
72
|
+
# also accepts verbatim (see ``_dialect_hint_for_source``).
|
|
73
|
+
_MODE_KWARGS = {
|
|
74
|
+
"fol": {},
|
|
75
|
+
"msfol": {"many_sorted": True},
|
|
76
|
+
"msfl": {"many_sorted": True, "fuzzy": True},
|
|
77
|
+
"fl": {"fuzzy": True},
|
|
78
|
+
"modal": {"modal": True},
|
|
79
|
+
"second_order": {"second_order": True},
|
|
80
|
+
"dependence": {"dependence": True},
|
|
81
|
+
"linear": {"linear": True},
|
|
82
|
+
"lambek": {"lambek": True},
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _render(node, fmt: str) -> str:
|
|
87
|
+
"""Render a parsed AST node in the requested output format.
|
|
88
|
+
|
|
89
|
+
The mapping mirrors the rendering methods defined on the Node base class;
|
|
90
|
+
``json`` emits the versioned envelope of
|
|
91
|
+
:func:`unicode_logic_kit.fol.serialize.serialize` (``schema_version`` + the
|
|
92
|
+
``to_dict()`` tree under ``root``) via :func:`json.dumps`.
|
|
93
|
+
"""
|
|
94
|
+
if fmt == "tree":
|
|
95
|
+
return node.tree_str()
|
|
96
|
+
if fmt == "unicode":
|
|
97
|
+
return node.to_unicode_str()
|
|
98
|
+
if fmt == "latex":
|
|
99
|
+
return node.to_latex()
|
|
100
|
+
if fmt == "tptp":
|
|
101
|
+
return node.to_tptp()
|
|
102
|
+
if fmt == "prover9":
|
|
103
|
+
return node.to_prover9()
|
|
104
|
+
if fmt == "smtlib":
|
|
105
|
+
return node.to_smtlib()
|
|
106
|
+
if fmt == "json":
|
|
107
|
+
from .fol.serialize import serialize
|
|
108
|
+
return json.dumps(serialize(node))
|
|
109
|
+
if fmt == "dot":
|
|
110
|
+
return node.to_dot()
|
|
111
|
+
# argparse restricts choices, so this is unreachable in normal use.
|
|
112
|
+
raise ValueError(f"Unknown output format: {fmt}")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
116
|
+
"""Construct the argparse parser for the legacy single-formula CLI."""
|
|
117
|
+
parser = argparse.ArgumentParser(
|
|
118
|
+
prog="unicode_logic_kit",
|
|
119
|
+
description="Parse and render a Unicode first-order logic formula.",
|
|
120
|
+
)
|
|
121
|
+
parser.add_argument(
|
|
122
|
+
"formula",
|
|
123
|
+
metavar="FORMULA",
|
|
124
|
+
help="the formula string to parse (e.g. \"∀x P(x)\")",
|
|
125
|
+
)
|
|
126
|
+
parser.add_argument(
|
|
127
|
+
"--mode",
|
|
128
|
+
choices=sorted(_MODE_KWARGS),
|
|
129
|
+
default="fol",
|
|
130
|
+
help=(
|
|
131
|
+
"parser dialect: fol (default), msfol, msfl, fl, modal, "
|
|
132
|
+
"second_order, dependence, linear, or lambek"
|
|
133
|
+
),
|
|
134
|
+
)
|
|
135
|
+
parser.add_argument(
|
|
136
|
+
"--to",
|
|
137
|
+
dest="to",
|
|
138
|
+
choices=["tree", "unicode", "latex", "tptp", "prover9", "smtlib", "json", "dot"],
|
|
139
|
+
default="tree",
|
|
140
|
+
help="output rendering (default: tree)",
|
|
141
|
+
)
|
|
142
|
+
return parser
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _run_legacy(argv) -> int:
|
|
146
|
+
"""The original (Tier 0) single-formula parse-and-render behaviour."""
|
|
147
|
+
parser = _build_parser()
|
|
148
|
+
args = parser.parse_args(argv)
|
|
149
|
+
|
|
150
|
+
formula_parser = MSFLParser(**_MODE_KWARGS[args.mode])
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
ast = formula_parser.parse(args.formula)
|
|
154
|
+
except (NamingError, ParsingError) as exc:
|
|
155
|
+
print(str(exc), file=sys.stderr)
|
|
156
|
+
return 1
|
|
157
|
+
|
|
158
|
+
try:
|
|
159
|
+
text = _render(ast, args.to)
|
|
160
|
+
except (NotImplementedError, ValueError) as exc:
|
|
161
|
+
# A renderer that refuses the formula is an unsupported fragment, the
|
|
162
|
+
# same failure the subcommand path reports (message on stderr, exit 3).
|
|
163
|
+
print(f"--to {args.to}: {exc}", file=sys.stderr)
|
|
164
|
+
return 3
|
|
165
|
+
print(text)
|
|
166
|
+
return 0
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
# ---------------------------------------------------------------------------
|
|
170
|
+
# Subcommands: a 1:1 mirror of unicode_logic_kit.api onto the command line.
|
|
171
|
+
# ---------------------------------------------------------------------------
|
|
172
|
+
|
|
173
|
+
_SUBCOMMANDS = ("check", "equiv", "prove", "countermodel", "repair", "translate")
|
|
174
|
+
|
|
175
|
+
# api.translate's logic labels that do not coincide with a parse_any dialect
|
|
176
|
+
# hint of the same name (see _dialect_hint_for_source).
|
|
177
|
+
_LOGIC_TO_DIALECT_HINT = {"team": "dependence"}
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _add_common(parser: argparse.ArgumentParser) -> None:
|
|
181
|
+
"""Add the three flags shared by every subcommand (see module docstring)."""
|
|
182
|
+
parser.add_argument(
|
|
183
|
+
"--dialect", default=None, metavar="DIALECT",
|
|
184
|
+
help="parse_any dialect hint (default: auto-detect)")
|
|
185
|
+
parser.add_argument(
|
|
186
|
+
"--json", dest="as_json", action="store_true",
|
|
187
|
+
help="machine-readable JSON output (default: human-readable summary)")
|
|
188
|
+
parser.add_argument(
|
|
189
|
+
"--timeout", type=int, default=10000, metavar="MS",
|
|
190
|
+
help="backend/solver timeout in milliseconds (default: 10000)")
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _parse_or_raise(text: str, dialect):
|
|
194
|
+
"""Parse ``text`` via :func:`api.parse_any`, raising ``ValueError`` on failure.
|
|
195
|
+
|
|
196
|
+
Used by every subcommand that needs an actual :class:`Node` to hand to a
|
|
197
|
+
decision API (``equiv``/``prove``/``countermodel``/``translate``) —
|
|
198
|
+
unlike ``check``/``repair``, which treat a parse failure as their own
|
|
199
|
+
diagnosis rather than a hard error (see module docstring).
|
|
200
|
+
"""
|
|
201
|
+
result = api.parse_any(text, hint=dialect)
|
|
202
|
+
if not result.ok:
|
|
203
|
+
message = (result.errors[-1]["message"] if result.errors
|
|
204
|
+
else f"no parser accepted {text!r}")
|
|
205
|
+
raise ValueError(f"could not parse {text!r}: {message}")
|
|
206
|
+
return result.formula
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _load_json_file(path: str) -> dict:
|
|
210
|
+
"""Load a JSON object from ``path`` (used for ``--signature`` files)."""
|
|
211
|
+
with open(path, "r", encoding="utf-8") as fh:
|
|
212
|
+
return json.load(fh)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _print_kv(pairs) -> None:
|
|
216
|
+
"""Print ``key: value`` lines — the shared shape of every human summary."""
|
|
217
|
+
for key, value in pairs:
|
|
218
|
+
print(f"{key}: {value}")
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
# -- check -------------------------------------------------------------- #
|
|
222
|
+
|
|
223
|
+
def _cmd_check(argv) -> int:
|
|
224
|
+
parser = argparse.ArgumentParser(
|
|
225
|
+
prog="unicode_logic_kit check",
|
|
226
|
+
description="Well-formedness report for a formula, optionally against a signature.")
|
|
227
|
+
parser.add_argument("formula", metavar="FORMULA")
|
|
228
|
+
parser.add_argument(
|
|
229
|
+
"--signature", default=None, metavar="SIG_JSON",
|
|
230
|
+
help="path to a JSON signature spec (see api.check / api._signature_errors)")
|
|
231
|
+
_add_common(parser)
|
|
232
|
+
args = parser.parse_args(argv)
|
|
233
|
+
|
|
234
|
+
signature = _load_json_file(args.signature) if args.signature else None
|
|
235
|
+
result = api.check(args.formula, signature=signature, dialect=args.dialect)
|
|
236
|
+
|
|
237
|
+
if args.as_json:
|
|
238
|
+
print(json.dumps(result.to_dict()))
|
|
239
|
+
else:
|
|
240
|
+
_print_kv([
|
|
241
|
+
("ok", result.ok),
|
|
242
|
+
("parseable", result.parseable),
|
|
243
|
+
("is_closed", result.is_closed),
|
|
244
|
+
("arity_consistent", result.arity_consistent),
|
|
245
|
+
("has_lambdas", result.has_lambdas),
|
|
246
|
+
])
|
|
247
|
+
if result.free_variables:
|
|
248
|
+
print("free_variables: " + ", ".join(result.free_variables))
|
|
249
|
+
if result.arity_conflicts:
|
|
250
|
+
print("arity_conflicts: " + json.dumps(list(result.arity_conflicts)))
|
|
251
|
+
if result.signature_errors:
|
|
252
|
+
print("signature_errors: " + json.dumps(list(result.signature_errors)))
|
|
253
|
+
if result.error:
|
|
254
|
+
print(f"error: {result.error}")
|
|
255
|
+
|
|
256
|
+
return 0 if result.ok else 1
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
# -- equiv -------------------------------------------------------------- #
|
|
260
|
+
|
|
261
|
+
def _cmd_equiv(argv) -> int:
|
|
262
|
+
parser = argparse.ArgumentParser(
|
|
263
|
+
prog="unicode_logic_kit equiv",
|
|
264
|
+
description="Graded equivalence between two formulas (api.equivalent).")
|
|
265
|
+
parser.add_argument("formula1", metavar="FORMULA1")
|
|
266
|
+
parser.add_argument("formula2", metavar="FORMULA2")
|
|
267
|
+
parser.add_argument(
|
|
268
|
+
"--method", choices=["exact", "canonical", "predicate_align", "solver", "auto"],
|
|
269
|
+
default="auto", help="equivalence level (default: auto, cheapest-first ladder)")
|
|
270
|
+
_add_common(parser)
|
|
271
|
+
args = parser.parse_args(argv)
|
|
272
|
+
|
|
273
|
+
f1 = _parse_or_raise(args.formula1, args.dialect)
|
|
274
|
+
f2 = _parse_or_raise(args.formula2, args.dialect)
|
|
275
|
+
|
|
276
|
+
result = api.equivalent(f1, f2, method=args.method, timeout=args.timeout)
|
|
277
|
+
|
|
278
|
+
if args.as_json:
|
|
279
|
+
print(json.dumps(result.to_dict()))
|
|
280
|
+
else:
|
|
281
|
+
_print_kv([("equivalent", result.equivalent), ("method_used", result.method_used)])
|
|
282
|
+
if result.counterexample is not None:
|
|
283
|
+
print("counterexample: " + json.dumps(result.counterexample))
|
|
284
|
+
|
|
285
|
+
if result.equivalent is True:
|
|
286
|
+
return 0
|
|
287
|
+
if result.equivalent is False:
|
|
288
|
+
return 1
|
|
289
|
+
return 2
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
# -- prove -------------------------------------------------------------- #
|
|
293
|
+
|
|
294
|
+
def _cmd_prove(argv) -> int:
|
|
295
|
+
parser = argparse.ArgumentParser(
|
|
296
|
+
prog="unicode_logic_kit prove",
|
|
297
|
+
description="Decide premises |= formula over a chain of backends (api.prove).")
|
|
298
|
+
parser.add_argument("formula", metavar="FORMULA")
|
|
299
|
+
parser.add_argument(
|
|
300
|
+
"--premise", action="append", default=[], metavar="P",
|
|
301
|
+
help="a premise formula (repeatable)")
|
|
302
|
+
parser.add_argument(
|
|
303
|
+
"--backends", default=None, metavar="a,b,c",
|
|
304
|
+
help="comma-separated backend names (default: the logic's default chain)")
|
|
305
|
+
parser.add_argument(
|
|
306
|
+
"--logic", default="auto", metavar="LOGIC",
|
|
307
|
+
help="'auto' (default, routes on modal operators), 'fol', or 'modal'")
|
|
308
|
+
_add_common(parser)
|
|
309
|
+
args = parser.parse_args(argv)
|
|
310
|
+
|
|
311
|
+
formula = _parse_or_raise(args.formula, args.dialect)
|
|
312
|
+
premises = [_parse_or_raise(p, args.dialect) for p in args.premise]
|
|
313
|
+
backends = ([b.strip() for b in args.backends.split(",") if b.strip()]
|
|
314
|
+
if args.backends else None)
|
|
315
|
+
|
|
316
|
+
verdict = api.prove(formula, premises, logic=args.logic, backends=backends,
|
|
317
|
+
timeout=args.timeout)
|
|
318
|
+
|
|
319
|
+
if args.as_json:
|
|
320
|
+
print(json.dumps(verdict.to_dict()))
|
|
321
|
+
else:
|
|
322
|
+
_print_kv([
|
|
323
|
+
("status", verdict.status),
|
|
324
|
+
("backend", verdict.backend),
|
|
325
|
+
("logic", verdict.logic),
|
|
326
|
+
("szs", verdict.szs_status),
|
|
327
|
+
("wall_time", verdict.wall_time),
|
|
328
|
+
])
|
|
329
|
+
if verdict.reason:
|
|
330
|
+
print(f"reason: {verdict.reason}")
|
|
331
|
+
if verdict.detail:
|
|
332
|
+
print(f"detail: {verdict.detail}")
|
|
333
|
+
|
|
334
|
+
return {"proved": 0, "refuted": 1, "unknown": 2, "error": 3}[verdict.status]
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
# -- countermodel --------------------------------------------------------- #
|
|
338
|
+
|
|
339
|
+
def _cmd_countermodel(argv) -> int:
|
|
340
|
+
parser = argparse.ArgumentParser(
|
|
341
|
+
prog="unicode_logic_kit countermodel",
|
|
342
|
+
description="Search for a countermodel to premises |= formula (api.countermodel).")
|
|
343
|
+
parser.add_argument("formula", metavar="FORMULA")
|
|
344
|
+
parser.add_argument(
|
|
345
|
+
"--premise", action="append", default=[], metavar="P",
|
|
346
|
+
help="a premise formula (repeatable)")
|
|
347
|
+
_add_common(parser)
|
|
348
|
+
args = parser.parse_args(argv)
|
|
349
|
+
|
|
350
|
+
formula = _parse_or_raise(args.formula, args.dialect)
|
|
351
|
+
premises = [_parse_or_raise(p, args.dialect) for p in args.premise]
|
|
352
|
+
|
|
353
|
+
result = api.countermodel(formula, premises, timeout=args.timeout)
|
|
354
|
+
|
|
355
|
+
if args.as_json:
|
|
356
|
+
print(json.dumps(result.to_dict()))
|
|
357
|
+
else:
|
|
358
|
+
_print_kv([("found", result.found), ("backend", result.backend)])
|
|
359
|
+
if result.explanation_nl:
|
|
360
|
+
print(f"explanation: {result.explanation_nl}")
|
|
361
|
+
if result.model is not None:
|
|
362
|
+
print("model: " + json.dumps(result.model))
|
|
363
|
+
|
|
364
|
+
return 0 if result.found else 1
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
# -- repair --------------------------------------------------------------- #
|
|
368
|
+
|
|
369
|
+
def _cmd_repair(argv) -> int:
|
|
370
|
+
parser = argparse.ArgumentParser(
|
|
371
|
+
prog="unicode_logic_kit repair",
|
|
372
|
+
description="One diagnose round over raw formula text, no fixer (api.repair).")
|
|
373
|
+
parser.add_argument("text", metavar="TEXT")
|
|
374
|
+
parser.add_argument(
|
|
375
|
+
"--signature", default=None, metavar="SIG_JSON",
|
|
376
|
+
help="path to a JSON signature spec (see api.check / api._signature_errors)")
|
|
377
|
+
_add_common(parser)
|
|
378
|
+
args = parser.parse_args(argv)
|
|
379
|
+
|
|
380
|
+
signature = _load_json_file(args.signature) if args.signature else None
|
|
381
|
+
step = next(api.repair(args.text, dialect=args.dialect, signature=signature,
|
|
382
|
+
fixer=None, max_attempts=1))
|
|
383
|
+
|
|
384
|
+
if args.as_json:
|
|
385
|
+
print(json.dumps(step.to_dict()))
|
|
386
|
+
else:
|
|
387
|
+
_print_kv([("ok", step.ok), ("converged", step.converged)])
|
|
388
|
+
if step.suggestion:
|
|
389
|
+
print(f"suggestion: {step.suggestion}")
|
|
390
|
+
print("diagnostics: " + json.dumps(step.diagnostics))
|
|
391
|
+
|
|
392
|
+
return 0 if step.ok else 1
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
# -- translate -------------------------------------------------------------- #
|
|
396
|
+
|
|
397
|
+
def _dialect_hint_for_source(from_logic: str, explicit):
|
|
398
|
+
"""Resolve the parse_any hint for translate's source term.
|
|
399
|
+
|
|
400
|
+
An explicit ``--dialect`` always wins. Otherwise: a ``from_logic`` that
|
|
401
|
+
names one of the kit's own unicode modes IS its own parse_any hint
|
|
402
|
+
(``"modal"``, ``"fol"``, …); ``"team"`` (the comorphism registry's label
|
|
403
|
+
for dependence-logic sentences) maps to the ``"dependence"`` mode, since
|
|
404
|
+
the two use different names for the same surface grammar and, unlike
|
|
405
|
+
``modal``/``fol``, auto-detection cannot disambiguate a dependence atom
|
|
406
|
+
``=(x,y)`` from a plain FOL equality atom of the same shape. Any other
|
|
407
|
+
label (``"alc"`` is handled separately before this is called; ``"eso"``
|
|
408
|
+
has no surface syntax of its own) falls back to auto-detection.
|
|
409
|
+
"""
|
|
410
|
+
if explicit is not None:
|
|
411
|
+
return explicit
|
|
412
|
+
if from_logic in _MODE_KWARGS:
|
|
413
|
+
return from_logic
|
|
414
|
+
return _LOGIC_TO_DIALECT_HINT.get(from_logic)
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def _render_translated(obj) -> str:
|
|
418
|
+
"""Render a translation result for the human-readable summary.
|
|
419
|
+
|
|
420
|
+
Every default-registry target (``fol``, ``modal``, ``eso``) is a
|
|
421
|
+
:class:`~unicode_logic_kit.fol.nodes.Node` (``to_unicode_str``); ALC
|
|
422
|
+
:class:`~unicode_logic_kit.dl.concepts.Concept` only ever appears as a
|
|
423
|
+
SOURCE in the default registry, but ``to_unicode``/``str`` cover it too
|
|
424
|
+
should a future edge target ``alc``.
|
|
425
|
+
"""
|
|
426
|
+
if hasattr(obj, "to_unicode_str"):
|
|
427
|
+
return obj.to_unicode_str()
|
|
428
|
+
if hasattr(obj, "to_unicode"):
|
|
429
|
+
return obj.to_unicode()
|
|
430
|
+
return str(obj)
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def _cmd_translate(argv) -> int:
|
|
434
|
+
parser = argparse.ArgumentParser(
|
|
435
|
+
prog="unicode_logic_kit translate",
|
|
436
|
+
description="Translate a term between logics via the comorphism registry (api.translate).")
|
|
437
|
+
parser.add_argument("formula", metavar="FORMULA")
|
|
438
|
+
parser.add_argument(
|
|
439
|
+
"--from", dest="from_logic", required=True, metavar="L1",
|
|
440
|
+
help="source logic label (e.g. 'fol', 'modal', 'alc', 'team')")
|
|
441
|
+
parser.add_argument(
|
|
442
|
+
"--to-logic", dest="to_logic", required=True, metavar="L2",
|
|
443
|
+
help="target logic label (e.g. 'fol', 'modal', 'eso') — NOT --to, "
|
|
444
|
+
"which the legacy parser already owns")
|
|
445
|
+
_add_common(parser)
|
|
446
|
+
args = parser.parse_args(argv)
|
|
447
|
+
|
|
448
|
+
if args.from_logic == "alc":
|
|
449
|
+
from .dl.parser import parse_concept
|
|
450
|
+
term = parse_concept(args.formula)
|
|
451
|
+
else:
|
|
452
|
+
dialect = _dialect_hint_for_source(args.from_logic, args.dialect)
|
|
453
|
+
term = _parse_or_raise(args.formula, dialect)
|
|
454
|
+
|
|
455
|
+
result = api.translate(term, args.from_logic, args.to_logic)
|
|
456
|
+
|
|
457
|
+
if args.as_json:
|
|
458
|
+
print(json.dumps(result.to_dict()))
|
|
459
|
+
else:
|
|
460
|
+
_print_kv([
|
|
461
|
+
("source", result.source),
|
|
462
|
+
("target", result.target),
|
|
463
|
+
("path", " -> ".join(result.path) if result.path else "(identity)"),
|
|
464
|
+
("lossy", result.lossy),
|
|
465
|
+
])
|
|
466
|
+
if result.note:
|
|
467
|
+
print(f"note: {result.note}")
|
|
468
|
+
print("result: " + _render_translated(result.result))
|
|
469
|
+
|
|
470
|
+
return 0
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
_SUBCOMMAND_HANDLERS = {
|
|
474
|
+
"check": _cmd_check,
|
|
475
|
+
"equiv": _cmd_equiv,
|
|
476
|
+
"prove": _cmd_prove,
|
|
477
|
+
"countermodel": _cmd_countermodel,
|
|
478
|
+
"repair": _cmd_repair,
|
|
479
|
+
"translate": _cmd_translate,
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def _run_subcommand(name: str, rest_argv) -> int:
|
|
484
|
+
"""Dispatch to one subcommand handler, enforcing the error culture.
|
|
485
|
+
|
|
486
|
+
argparse usage errors (missing/unknown flags) still raise ``SystemExit``
|
|
487
|
+
straight through, exactly like the legacy parser — those are the user
|
|
488
|
+
misusing the CLI, not a runtime failure to report cleanly. Everything
|
|
489
|
+
else that a handler can raise on a bad-but-well-formed request (an
|
|
490
|
+
unparseable formula from ``_parse_or_raise``, an unknown/unavailable
|
|
491
|
+
backend, an unsupported translation fragment, a malformed
|
|
492
|
+
``--signature`` file) is caught here, printed as one clean message, and
|
|
493
|
+
turned into exit code 3.
|
|
494
|
+
"""
|
|
495
|
+
handler = _SUBCOMMAND_HANDLERS[name]
|
|
496
|
+
try:
|
|
497
|
+
return handler(rest_argv)
|
|
498
|
+
except (ValueError, BackendUnavailable, NotImplementedError, OSError) as exc:
|
|
499
|
+
print(f"{name}: {exc}", file=sys.stderr)
|
|
500
|
+
return 3
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def main(argv=None) -> int:
|
|
504
|
+
"""Run the CLI: dispatch to a subcommand, or fall back to the legacy parser.
|
|
505
|
+
|
|
506
|
+
Parses ``argv`` (defaults to ``sys.argv[1:]``). If ``argv`` is non-empty
|
|
507
|
+
and ``argv[0]`` is one of ``check``/``equiv``/``prove``/``countermodel``/
|
|
508
|
+
``repair``/``translate``, the rest of ``argv`` is handed to that
|
|
509
|
+
subcommand; otherwise the whole of ``argv`` goes to the legacy
|
|
510
|
+
single-formula parse-and-render path (see module docstring for both).
|
|
511
|
+
"""
|
|
512
|
+
if argv is None:
|
|
513
|
+
argv = sys.argv[1:]
|
|
514
|
+
if argv and argv[0] in _SUBCOMMANDS:
|
|
515
|
+
return _run_subcommand(argv[0], argv[1:])
|
|
516
|
+
return _run_legacy(argv)
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
if __name__ == "__main__":
|
|
520
|
+
sys.exit(main())
|