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,525 @@
|
|
|
1
|
+
"""QMLTP input: read QMLTP (Quantified Modal Logic Theorem Proving library) ``.p``
|
|
2
|
+
problems into the kit AST.
|
|
3
|
+
|
|
4
|
+
QMLTP (Raths & Otten, "Building a Problem Library for First-Order Modal
|
|
5
|
+
Logics", TABLEAUX 2009; Raths & Otten, "The QMLTP Problem Library for
|
|
6
|
+
First-Order Modal Logics", CADE 2012) is a 600-problem benchmark for
|
|
7
|
+
first-order modal logic, in an extension of plain TPTP ``fof`` syntax. This
|
|
8
|
+
is a SEPARATE, only loosely-related dialect from NXF (see
|
|
9
|
+
:mod:`unicode_logic_kit.atp.tptp_ncl`): QMLTP predates NXF, uses its own
|
|
10
|
+
statement keyword and unary modal connectives, and records a problem's
|
|
11
|
+
per-logic/per-domain-regime status in a structured comment header rather than
|
|
12
|
+
NXF's ``$modal``/``$domains`` logic-specification statement.
|
|
13
|
+
|
|
14
|
+
Verified DIRECTLY against the real library (downloaded and inspected
|
|
15
|
+
2026-09-18 from ``https://www.iltp.de/qmltp/download/QMLTP-v1.1.tar.gz`` —
|
|
16
|
+
see "License" below) before writing a line of this module, rather than
|
|
17
|
+
assumed from the roadmap description that named this module, which turned
|
|
18
|
+
out to describe QMLTP's syntax and header format INCORRECTLY in two ways
|
|
19
|
+
corrected here (see the deviations noted at each point):
|
|
20
|
+
|
|
21
|
+
* **Statement keyword is ``qmf``, not ``fof``.** Every QMLTP problem file's
|
|
22
|
+
formula statements read ``qmf(name, role, formula).`` — literally,
|
|
23
|
+
verbatim, in all 600 problems (confirmed: ``grep -c '^qmf('`` across the
|
|
24
|
+
whole library's ``Problems/`` tree returns exactly 600 hits, zero for
|
|
25
|
+
``^fof(``). This reader therefore accepts ONLY ``qmf(...)``, refusing
|
|
26
|
+
``fof``/``cnf``/``tff`` (and QMLTP's own ``tpi(...)`` — see below) by
|
|
27
|
+
name.
|
|
28
|
+
* **The modal connectives are ``#box``/``#dia`` (a ``#``-prefixed keyword
|
|
29
|
+
applied with ``:``), not the ``box:(...)``/``dia:(...)`` (no ``#``) the
|
|
30
|
+
roadmap item that named this module described** — e.g. SYM002+1.p's
|
|
31
|
+
conjecture reads ``( (#box : ( ! [X] : ( f(X) ) ) ) => ( ! [X] : (#box :
|
|
32
|
+
( f(X) ) ) ) )``. This is the syntax the grammar extension below actually
|
|
33
|
+
implements.
|
|
34
|
+
* **Multi-modal problems (the ``MML`` domain, 20 of the 600) use a
|
|
35
|
+
DIFFERENT top-level statement, ``tpi(1,set_logic,modal([...],[(name,
|
|
36
|
+
system), ...])).``, to declare one or more NAMED modalities before using
|
|
37
|
+
indexed connectives ``#box(name)``/``#dia(name)`` for each** — e.g.
|
|
38
|
+
``#box(fool)``, ``#box(a)``. This is itself a genuine QMLTP construct
|
|
39
|
+
(unlike the roadmap's guess of a NXF-style integer index), but its
|
|
40
|
+
``set_logic`` payload (``modal([cumulative,rigid,local], [(fool,s4),
|
|
41
|
+
(a,s4), ...])``) does not fit this reader's reused ``fof``/``tff`` term
|
|
42
|
+
grammar (it needs bracketed lists and name/system pairs as TERMS, which
|
|
43
|
+
plain TPTP terms are not) — refused by name, see "Scope" below, rather
|
|
44
|
+
than guessed at.
|
|
45
|
+
|
|
46
|
+
Public API: :func:`parse_qmltp_formula` (one bare formula), :func:`parse_qmltp`
|
|
47
|
+
/ :func:`load_qmltp` (a whole problem: its formulas AND its parsed header
|
|
48
|
+
metadata, combined — see "Deviation from ``fol.tptp_input``'s shape" below),
|
|
49
|
+
and the :class:`QmltpFormula` / :class:`QmltpHeader` / :class:`QmltpStatus` /
|
|
50
|
+
:class:`QmltpProblem` records they return.
|
|
51
|
+
|
|
52
|
+
Grammar reuse (per this item's own instructions: reuse
|
|
53
|
+
:mod:`unicode_logic_kit.fol.tptp_input`'s Lark grammar rather than rewrite
|
|
54
|
+
one). :mod:`fol.tptp_input`'s ``stmt`` production does not hard-code the
|
|
55
|
+
statement keyword in the GRAMMAR at all — ``LOWER "(" fof_name "," LOWER ","
|
|
56
|
+
formula ...")" "."`` accepts any lower-case keyword, and it is
|
|
57
|
+
``_TptpTransformer.stmt`` that checks for ``fof``/``cnf``/``tff`` — so
|
|
58
|
+
``qmf(...)`` already parses under the UNMODIFIED base grammar; this module's
|
|
59
|
+
:data:`_GRAMMAR` only ADDS four new ``?unary`` alternatives (``#box:(...)``,
|
|
60
|
+
``#dia:(...)``, and the indexed forms, refused by name once recognised — see
|
|
61
|
+
above) to :data:`fol.tptp_input._GRAMMAR`'s own text, and
|
|
62
|
+
:class:`_QmltpTransformer` SUBCLASSES :class:`fol.tptp_input._TptpTransformer`,
|
|
63
|
+
inheriting every classical connective/quantifier/atom/term rule UNCHANGED and
|
|
64
|
+
overriding only ``stmt`` (accept ``qmf``, not ``fof``/``cnf``/``tff``),
|
|
65
|
+
``stmt_type``/``include_stmt`` (QMLTP has neither TFF type declarations nor
|
|
66
|
+
``include(...)`` — refused by name), and the four new modal rules. This
|
|
67
|
+
reader can therefore never disagree with :mod:`fol.tptp_input` about how to
|
|
68
|
+
read a classical connective, quantifier or term — the SAME code decides both.
|
|
69
|
+
|
|
70
|
+
Scope — the mono-modal alethic fragment only, matching
|
|
71
|
+
:mod:`unicode_logic_kit.atp.tptp_ncl`'s own writer-side boundary (see that
|
|
72
|
+
module's docstring):
|
|
73
|
+
|
|
74
|
+
* Supported: ``qmf(name, role, formula).`` statements over
|
|
75
|
+
¬/∧/∨/→/↔/=/!=, ``!``/``?`` quantifiers (UNSORTED — no QMLTP problem in
|
|
76
|
+
the library uses a typed quantifier; every one reads plain ``! [X] :
|
|
77
|
+
...``), and the unindexed ``#box``/``#dia`` connectives.
|
|
78
|
+
* NOT supported, each raising :class:`NotImplementedError` naming the
|
|
79
|
+
construct (never silently dropped or approximated):
|
|
80
|
+
- ``tpi(...)`` multi-modal ``set_logic`` directives, and the indexed
|
|
81
|
+
``#box(name)``/``#dia(name)`` connectives that depend on one (the
|
|
82
|
+
``MML`` domain) — see "the roadmap's guess" note above.
|
|
83
|
+
- ``include(...)`` directives and ``tff(...,type,...)`` declarations —
|
|
84
|
+
neither construct occurs anywhere in the library, so both are refused
|
|
85
|
+
rather than silently mis-parsed if one ever appeared.
|
|
86
|
+
|
|
87
|
+
License
|
|
88
|
+
-------
|
|
89
|
+
No redistribution licence for the QMLTP library itself could be found (as of
|
|
90
|
+
2026-09-18, neither the v1.1 archive ``QMLTP-v1.1.tar.gz`` nor
|
|
91
|
+
``https://www.iltp.de/qmltp/`` states one). This module therefore ships no
|
|
92
|
+
QMLTP file: it was developed and checked against the real v1.1 problems, and
|
|
93
|
+
the test suite uses small stand-ins written for it in the same syntax and
|
|
94
|
+
header layout (``tests/fixtures/qmltp``), each with a hand-derived status
|
|
95
|
+
table re-checked against :func:`~unicode_logic_kit.fol.qml.qml_is_valid`. Point
|
|
96
|
+
:func:`load_qmltp` at your own copy of the library to read real problems.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
import re
|
|
100
|
+
from dataclasses import dataclass
|
|
101
|
+
from typing import Optional, Tuple
|
|
102
|
+
|
|
103
|
+
from lark import Lark
|
|
104
|
+
from lark.exceptions import VisitError
|
|
105
|
+
|
|
106
|
+
from .nodes import Node, Box, Diamond
|
|
107
|
+
from .naming import ParsingError
|
|
108
|
+
from .tptp_input import (
|
|
109
|
+
_GRAMMAR as _TPTP_GRAMMAR, _TptpTransformer, _functor_name,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
__all__ = [
|
|
113
|
+
"QmltpParsingError", "QmltpFormula", "QmltpStatus", "QmltpHeader",
|
|
114
|
+
"QmltpProblem", "parse_qmltp_formula", "parse_qmltp", "load_qmltp",
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class QmltpParsingError(ParsingError):
|
|
119
|
+
"""A QMLTP import failure, carrying a plain message.
|
|
120
|
+
|
|
121
|
+
The exact mirror of :class:`fol.tptp_input.TptpParsingError` — subclasses
|
|
122
|
+
:class:`ParsingError` (so ``except ParsingError`` still catches it) but
|
|
123
|
+
takes a string instead of a Lark exception.
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
def __init__(self, message: str):
|
|
127
|
+
self.args = (message,)
|
|
128
|
+
|
|
129
|
+
def __str__(self):
|
|
130
|
+
return self.args[0]
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# =============================================================================
|
|
134
|
+
# Grammar: fol.tptp_input's own grammar text, extended with #box/#dia.
|
|
135
|
+
# =============================================================================
|
|
136
|
+
|
|
137
|
+
# The exact anchor line fol.tptp_input._GRAMMAR uses for its "~" (negation)
|
|
138
|
+
# alternative -- the new QMLTP modal alternatives are inserted right after
|
|
139
|
+
# it. A drift guard (the count() check below) turns a future, unrelated
|
|
140
|
+
# change to that grammar text into a loud ImportError here rather than a
|
|
141
|
+
# silent "#box/#dia never actually parse" failure.
|
|
142
|
+
_UNARY_ANCHOR = '?unary: "~" unary -> neg\n'
|
|
143
|
+
_QMLTP_UNARY_EXTRA = (
|
|
144
|
+
' | "#box" ":" "(" formula ")" -> box\n'
|
|
145
|
+
' | "#dia" ":" "(" formula ")" -> dia\n'
|
|
146
|
+
' | "#box" "(" LOWER ")" ":" "(" formula ")" -> box_indexed\n'
|
|
147
|
+
' | "#dia" "(" LOWER ")" ":" "(" formula ")" -> dia_indexed\n'
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
if _TPTP_GRAMMAR.count(_UNARY_ANCHOR) != 1:
|
|
151
|
+
raise ImportError(
|
|
152
|
+
"fol.qmltp_input: fol.tptp_input's grammar text no longer contains "
|
|
153
|
+
"the exact anchor line this module extends ('?unary: \"~\" unary "
|
|
154
|
+
"-> neg') -- fol.tptp_input.py must have changed; update "
|
|
155
|
+
"_UNARY_ANCHOR in fol/qmltp_input.py to match.")
|
|
156
|
+
|
|
157
|
+
_GRAMMAR = _TPTP_GRAMMAR.replace(_UNARY_ANCHOR, _UNARY_ANCHOR + _QMLTP_UNARY_EXTRA, 1)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class _QmltpTransformer(_TptpTransformer):
|
|
161
|
+
"""Extends :class:`fol.tptp_input._TptpTransformer` with QMLTP's own
|
|
162
|
+
statement keyword and modal connectives.
|
|
163
|
+
|
|
164
|
+
Every classical rule (connectives, quantifiers, atoms, terms) is
|
|
165
|
+
inherited UNCHANGED — only the five methods below are new or overridden;
|
|
166
|
+
see the module docstring's "Grammar reuse" section.
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
def stmt(self, items):
|
|
170
|
+
"""``qmf(name, role, formula).`` -> :class:`QmltpFormula`.
|
|
171
|
+
|
|
172
|
+
Anything else at the top level (``fof``/``cnf``/``tff``, or
|
|
173
|
+
QMLTP's own ``tpi(...)`` multi-modal directive — which this
|
|
174
|
+
base grammar cannot even fully parse, see the module docstring's
|
|
175
|
+
"Statement keyword" note, but a short ``tpi(name,...)`` prefix
|
|
176
|
+
CAN reach this far before its payload fails to match `formula`)
|
|
177
|
+
is refused BY NAME.
|
|
178
|
+
"""
|
|
179
|
+
keyword = str(items[0])
|
|
180
|
+
if keyword == "tpi":
|
|
181
|
+
raise NotImplementedError(
|
|
182
|
+
"qmltp_input: 'tpi(...)' multi-modal set_logic directives "
|
|
183
|
+
"(the MML domain) are outside this reader's supported "
|
|
184
|
+
"mono-modal fragment -- see module docstring 'Scope'.")
|
|
185
|
+
if keyword != "qmf":
|
|
186
|
+
raise QmltpParsingError(
|
|
187
|
+
f"SYNTAX_ERROR: only 'qmf(...)' statements are QMLTP "
|
|
188
|
+
f"formulas (got '{keyword}(...)').")
|
|
189
|
+
name, role, formula = str(items[1]), str(items[2]), items[3]
|
|
190
|
+
return QmltpFormula(name, role, formula)
|
|
191
|
+
|
|
192
|
+
def stmt_type(self, items):
|
|
193
|
+
raise QmltpParsingError(
|
|
194
|
+
"SYNTAX_ERROR: a TFF-style type declaration "
|
|
195
|
+
"('name,type,...') is not part of the QMLTP dialect -- no "
|
|
196
|
+
"problem in the library uses one.")
|
|
197
|
+
|
|
198
|
+
def include_stmt(self, items):
|
|
199
|
+
raise QmltpParsingError(
|
|
200
|
+
"SYNTAX_ERROR: include(...) directives are not supported by "
|
|
201
|
+
"this QMLTP reader -- no problem in the library uses one.")
|
|
202
|
+
|
|
203
|
+
def box(self, items):
|
|
204
|
+
return Box(items[0])
|
|
205
|
+
|
|
206
|
+
def dia(self, items):
|
|
207
|
+
return Diamond(items[0])
|
|
208
|
+
|
|
209
|
+
def box_indexed(self, items):
|
|
210
|
+
name = _functor_name(items[0])
|
|
211
|
+
raise NotImplementedError(
|
|
212
|
+
f"qmltp_input: indexed multi-modal '#box({name})' is outside "
|
|
213
|
+
"this reader's supported mono-modal alethic fragment -- see "
|
|
214
|
+
"module docstring 'Scope'. Only the unindexed #box/#dia are "
|
|
215
|
+
"supported in this release.")
|
|
216
|
+
|
|
217
|
+
def dia_indexed(self, items):
|
|
218
|
+
name = _functor_name(items[0])
|
|
219
|
+
raise NotImplementedError(
|
|
220
|
+
f"qmltp_input: indexed multi-modal '#dia({name})' is outside "
|
|
221
|
+
"this reader's supported mono-modal alethic fragment -- see "
|
|
222
|
+
"module docstring 'Scope'. Only the unindexed #box/#dia are "
|
|
223
|
+
"supported in this release.")
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
_FORMULA_PARSER = Lark(_GRAMMAR, start="formula", parser="earley")
|
|
227
|
+
_FILE_PARSER = Lark(_GRAMMAR, start="file", parser="earley")
|
|
228
|
+
_TRANSFORMER = _QmltpTransformer()
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _parse(text: str, parser: Lark, what: str):
|
|
232
|
+
"""Parse + transform with unified error handling (unwrapping lark's
|
|
233
|
+
VisitError) -- the exact mirror of fol.tptp_input._parse."""
|
|
234
|
+
try:
|
|
235
|
+
tree = parser.parse(text)
|
|
236
|
+
except QmltpParsingError:
|
|
237
|
+
raise
|
|
238
|
+
except Exception as exc:
|
|
239
|
+
raise QmltpParsingError(f"SYNTAX_ERROR: could not parse QMLTP {what}: {exc}")
|
|
240
|
+
try:
|
|
241
|
+
return _TRANSFORMER.transform(tree)
|
|
242
|
+
except VisitError as exc:
|
|
243
|
+
original = exc.orig_exc
|
|
244
|
+
if isinstance(original, (ParsingError, NotImplementedError)):
|
|
245
|
+
raise original
|
|
246
|
+
raise QmltpParsingError(f"SYNTAX_ERROR: in QMLTP {what}: {original}")
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
# =============================================================================
|
|
250
|
+
# Formula / header records
|
|
251
|
+
# =============================================================================
|
|
252
|
+
|
|
253
|
+
@dataclass(frozen=True)
|
|
254
|
+
class QmltpFormula:
|
|
255
|
+
"""One annotated QMLTP statement: its ``name``, ``role``, and parsed ``formula``."""
|
|
256
|
+
|
|
257
|
+
name: str
|
|
258
|
+
role: str
|
|
259
|
+
formula: Node
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
@dataclass(frozen=True)
|
|
263
|
+
class QmltpStatus:
|
|
264
|
+
"""One ``(logic, domain) -> verdict`` cell from a QMLTP ``% Status`` table.
|
|
265
|
+
|
|
266
|
+
``logic`` is one of ``"K"``, ``"D"``, ``"T"``, ``"S4"``, ``"S5"``
|
|
267
|
+
(the literal row label QMLTP's header prints — matching
|
|
268
|
+
:mod:`unicode_logic_kit.fol.qml`'s own ``frame=`` names one-to-one, so a
|
|
269
|
+
cell reads straight into ``qml_is_valid(..., frame=status.logic)``);
|
|
270
|
+
``domain`` is one of ``"varying"``, ``"cumulative"``, ``"constant"``
|
|
271
|
+
(the literal column label — matching ``qml_is_valid``'s ``mode=`` names,
|
|
272
|
+
since ``"cumulative"`` is itself one of :mod:`fol.qml`'s accepted mode
|
|
273
|
+
spellings, an alias of ``"increasing"``); ``verdict`` is the literal
|
|
274
|
+
recorded status string, kept AS-IS (``"Theorem"``, ``"Non-Theorem"``,
|
|
275
|
+
or occasionally ``"Unsolved"``/``"Open"``), never normalised or
|
|
276
|
+
validated against a fixed vocabulary.
|
|
277
|
+
"""
|
|
278
|
+
|
|
279
|
+
logic: str
|
|
280
|
+
domain: str
|
|
281
|
+
verdict: str
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
@dataclass(frozen=True)
|
|
285
|
+
class QmltpHeader:
|
|
286
|
+
"""Metadata recovered from a QMLTP problem file's ``%`` comment header.
|
|
287
|
+
|
|
288
|
+
``file`` / ``domain`` / ``problem`` / ``source`` are ``None`` when the
|
|
289
|
+
corresponding ``% <Field> : ...`` line is absent. ``statuses`` holds
|
|
290
|
+
every ``(logic, domain) -> verdict`` cell of the ``% Status`` table (see
|
|
291
|
+
:class:`QmltpStatus`), in the file's own row/column order; empty if no
|
|
292
|
+
such table was found (e.g. a multi-modal problem's single-line ``%
|
|
293
|
+
Status : Theorem`` form, which this reader does not parse -- see module
|
|
294
|
+
docstring 'Scope', since multi-modal problems are refused before a
|
|
295
|
+
header lookup would matter). ``comments`` holds every raw ``%`` line
|
|
296
|
+
verbatim (stripped of its trailing newline), in source order.
|
|
297
|
+
"""
|
|
298
|
+
|
|
299
|
+
file: Optional[str]
|
|
300
|
+
domain: Optional[str]
|
|
301
|
+
problem: Optional[str]
|
|
302
|
+
source: Optional[str]
|
|
303
|
+
statuses: Tuple[QmltpStatus, ...]
|
|
304
|
+
comments: Tuple[str, ...]
|
|
305
|
+
|
|
306
|
+
def status_for(self, logic: str, domain: str) -> str:
|
|
307
|
+
"""The recorded verdict for ``(logic, domain)``, e.g. ``status_for("K",
|
|
308
|
+
"constant")`` -> ``"Theorem"``.
|
|
309
|
+
|
|
310
|
+
Raises:
|
|
311
|
+
KeyError: no cell for that exact ``(logic, domain)`` pair was
|
|
312
|
+
recorded (an unknown logic/domain name, or a header this
|
|
313
|
+
reader could not parse a status table from at all).
|
|
314
|
+
"""
|
|
315
|
+
for status in self.statuses:
|
|
316
|
+
if status.logic == logic and status.domain == domain:
|
|
317
|
+
return status.verdict
|
|
318
|
+
available = sorted({(s.logic, s.domain) for s in self.statuses})
|
|
319
|
+
raise KeyError(
|
|
320
|
+
f"qmltp_input: no recorded status for logic={logic!r}, "
|
|
321
|
+
f"domain={domain!r} in this header (available: {available}).")
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
@dataclass(frozen=True)
|
|
325
|
+
class QmltpProblem:
|
|
326
|
+
"""A whole QMLTP problem: its parsed ``formulas`` plus the recovered ``header``.
|
|
327
|
+
|
|
328
|
+
Deviation from :mod:`fol.tptp_input`'s shape: that module splits "just
|
|
329
|
+
the formulas" (:func:`~fol.tptp_input.parse_tptp`, a bare list) from
|
|
330
|
+
"formulas plus header" (:func:`~fol.tptp_input.parse_tptp_problem`, a
|
|
331
|
+
combined record), because a plain TPTP problem's header is optional,
|
|
332
|
+
decorative metadata. A QMLTP problem's header is NOT decorative — it
|
|
333
|
+
carries the per-logic/per-domain ``Theorem``/``Non-Theorem`` status that
|
|
334
|
+
is this whole format's reason for existing (see this item's own
|
|
335
|
+
test_oracle: reading a problem WITHOUT its status is reading half of
|
|
336
|
+
it), so :func:`parse_qmltp` / :func:`load_qmltp` return this combined
|
|
337
|
+
record directly rather than offering a "formulas only" variant nothing
|
|
338
|
+
would realistically use.
|
|
339
|
+
"""
|
|
340
|
+
|
|
341
|
+
formulas: Tuple[QmltpFormula, ...]
|
|
342
|
+
header: QmltpHeader
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def parse_qmltp_formula(text: str) -> Node:
|
|
346
|
+
"""Parse a single bare QMLTP formula (no ``qmf(...)`` wrapper) into a Node.
|
|
347
|
+
|
|
348
|
+
Args:
|
|
349
|
+
text: a QMLTP formula, e.g. ``"#box : ( ! [X] : ( f(X) ) )"``.
|
|
350
|
+
|
|
351
|
+
Returns:
|
|
352
|
+
The formula as a toolkit :class:`Node` (``#box``/``#dia`` become
|
|
353
|
+
:class:`~fol.nodes.Box` / :class:`~fol.nodes.Diamond`; everything
|
|
354
|
+
else parses exactly as :func:`fol.tptp_input.parse_tptp_formula`
|
|
355
|
+
would).
|
|
356
|
+
|
|
357
|
+
Raises:
|
|
358
|
+
ParsingError: ``text`` is not a well-formed QMLTP formula.
|
|
359
|
+
NotImplementedError: ``text`` uses an indexed ``#box(...)``/
|
|
360
|
+
``#dia(...)`` connective — see module docstring 'Scope'.
|
|
361
|
+
"""
|
|
362
|
+
return _parse(text, _FORMULA_PARSER, "formula")
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
# A header field line: optional leading whitespace, '%', optional whitespace,
|
|
366
|
+
# one of the four recognised QMLTP field names (matched case-sensitively),
|
|
367
|
+
# optional whitespace, ':', optional whitespace, then the rest of the line as
|
|
368
|
+
# the raw value. Mirrors fol.tptp_input._HEADER_FIELD_RE, with QMLTP's own
|
|
369
|
+
# extra "Source" field and WITHOUT "Status"/"Rating" (QMLTP's Status is a
|
|
370
|
+
# multi-row TABLE, not a single value -- parsed separately below).
|
|
371
|
+
_QMLTP_FIELD_RE = re.compile(r"^\s*%\s*(File|Domain|Problem|Source)\s*:\s*(.*)$")
|
|
372
|
+
# The "% Status : varying cumulative constant" header line --
|
|
373
|
+
# group(1) captures the column (domain-condition) labels, whitespace-joined.
|
|
374
|
+
_QMLTP_STATUS_HEADER_RE = re.compile(r"^\s*%\s*Status\s*:\s*(.*)$")
|
|
375
|
+
# One status TABLE row: "% K Non-Theorem Theorem Theorem".
|
|
376
|
+
# The row label is restricted to the five logic names QMLTP actually uses,
|
|
377
|
+
# so this can never accidentally match the "% Rating :" header line that
|
|
378
|
+
# immediately follows the table (its own row label would be "Rating", which
|
|
379
|
+
# this pattern does not accept) or a Rating-table row (whose FIRST token
|
|
380
|
+
# after the logic-name label is a float, not a Theorem/Non-Theorem word --
|
|
381
|
+
# though the label check alone already prevents any bleed-through, since a
|
|
382
|
+
# rating row starts "% K 0.50 ..." and IS syntactically a candidate match
|
|
383
|
+
# here; the boundary that actually stops the scan is the "% Rating :" HEADER
|
|
384
|
+
# line between the two tables, which this pattern does not match at all --
|
|
385
|
+
# see _parse_qmltp_header's row-consuming loop).
|
|
386
|
+
_QMLTP_STATUS_ROW_RE = re.compile(r"^\s*%\s*(K|D|T|S4|S5)\s+(.*)$")
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def _parse_qmltp_header(text: str) -> QmltpHeader:
|
|
390
|
+
"""Scan raw QMLTP text for its ``%`` header block, independent of the
|
|
391
|
+
grammar -- the QMLTP counterpart of
|
|
392
|
+
:func:`fol.tptp_input._parse_tptp_header`, with its own regex-based
|
|
393
|
+
parser for the ``% Status`` TABLE (QMLTP's header shape, verified
|
|
394
|
+
directly against the real library, is a multi-row/multi-column table,
|
|
395
|
+
not the single ``% Status : Theorem`` line a plain TPTP problem or the
|
|
396
|
+
roadmap description that named this module would suggest -- see module
|
|
397
|
+
docstring).
|
|
398
|
+
|
|
399
|
+
Every line whose first non-whitespace character is ``%`` is collected
|
|
400
|
+
verbatim (trailing newline stripped) into ``comments``, in source
|
|
401
|
+
order. Among those: a ``File``/``Domain``/``Problem``/``Source`` field
|
|
402
|
+
line populates the matching :class:`QmltpHeader` attribute (first
|
|
403
|
+
occurrence wins); the FIRST ``% Status :`` line starts the status-table
|
|
404
|
+
scan, which reads the header's own column (domain-condition) labels and
|
|
405
|
+
then consumes every immediately-following ``% <logic> <verdict>...``
|
|
406
|
+
row, stopping at the first ``%`` line that is not one (the blank ``%``
|
|
407
|
+
separator that precedes ``% Rating`` in every problem checked, though
|
|
408
|
+
the row pattern's restriction to the five known logic names would stop
|
|
409
|
+
it there regardless -- see :data:`_QMLTP_STATUS_ROW_RE`).
|
|
410
|
+
|
|
411
|
+
Args:
|
|
412
|
+
text: the raw contents of a QMLTP problem file (or any QMLTP text).
|
|
413
|
+
|
|
414
|
+
Returns:
|
|
415
|
+
A :class:`QmltpHeader`; every field is ``None``, ``statuses`` is
|
|
416
|
+
``()`` and ``comments`` is ``()`` if the text has no ``%`` lines at
|
|
417
|
+
all (or none matching the recognised shapes).
|
|
418
|
+
"""
|
|
419
|
+
comments = []
|
|
420
|
+
fields = {}
|
|
421
|
+
statuses = []
|
|
422
|
+
lines = text.splitlines()
|
|
423
|
+
i, n = 0, len(lines)
|
|
424
|
+
status_table_seen = False
|
|
425
|
+
while i < n:
|
|
426
|
+
line = lines[i]
|
|
427
|
+
if not line.lstrip().startswith("%"):
|
|
428
|
+
i += 1
|
|
429
|
+
continue
|
|
430
|
+
comments.append(line)
|
|
431
|
+
|
|
432
|
+
field_match = _QMLTP_FIELD_RE.match(line)
|
|
433
|
+
if field_match:
|
|
434
|
+
field, value = field_match.group(1), field_match.group(2)
|
|
435
|
+
if field not in fields:
|
|
436
|
+
fields[field] = value.strip()
|
|
437
|
+
i += 1
|
|
438
|
+
continue
|
|
439
|
+
|
|
440
|
+
status_header_match = _QMLTP_STATUS_HEADER_RE.match(line)
|
|
441
|
+
if status_header_match and not status_table_seen:
|
|
442
|
+
status_table_seen = True
|
|
443
|
+
domain_names = status_header_match.group(1).split()
|
|
444
|
+
i += 1
|
|
445
|
+
while i < n:
|
|
446
|
+
row_line = lines[i]
|
|
447
|
+
if not row_line.lstrip().startswith("%"):
|
|
448
|
+
break
|
|
449
|
+
row_match = _QMLTP_STATUS_ROW_RE.match(row_line)
|
|
450
|
+
if not row_match:
|
|
451
|
+
break
|
|
452
|
+
comments.append(row_line)
|
|
453
|
+
logic = row_match.group(1)
|
|
454
|
+
tokens = row_match.group(2).split()
|
|
455
|
+
for domain_name, verdict in zip(domain_names, tokens):
|
|
456
|
+
statuses.append(QmltpStatus(logic, domain_name, verdict))
|
|
457
|
+
i += 1
|
|
458
|
+
continue
|
|
459
|
+
|
|
460
|
+
i += 1
|
|
461
|
+
|
|
462
|
+
return QmltpHeader(
|
|
463
|
+
file=fields.get("File"),
|
|
464
|
+
domain=fields.get("Domain"),
|
|
465
|
+
problem=fields.get("Problem"),
|
|
466
|
+
source=fields.get("Source"),
|
|
467
|
+
statuses=tuple(statuses),
|
|
468
|
+
comments=tuple(comments),
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
# A top-level "tpi(...)" statement's own payload (a bracketed
|
|
473
|
+
# name/system-pair list, e.g. "modal([cumulative,rigid,local],
|
|
474
|
+
# [(fool,s4),(a,s4)])") does not fit this reader's reused fof/tff TERM
|
|
475
|
+
# grammar at all (a "[...]" list or a "(name,system)" pair is not a legal
|
|
476
|
+
# TPTP term) -- so a real multi-modal file fails to parse with a generic,
|
|
477
|
+
# unhelpful "no terminal matches '['" syntax error before _QmltpTransformer
|
|
478
|
+
# .stmt ever runs (that method's own "keyword == 'tpi'" check is kept as a
|
|
479
|
+
# defensive fallback for the rare tpi(...) whose payload WOULD otherwise
|
|
480
|
+
# parse). This cheap pre-scan catches the real construct up front and
|
|
481
|
+
# refuses it BY NAME instead.
|
|
482
|
+
# (?:^|(?<=\r)): a line also starts after a bare CR, which (?m)^ ignores.
|
|
483
|
+
_TPI_STATEMENT_RE = re.compile(r"(?:^|(?<=\r))\s*tpi\s*\(", re.MULTILINE)
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def parse_qmltp(text: str) -> QmltpProblem:
|
|
487
|
+
"""Parse a whole QMLTP problem into its formulas *and* its header metadata.
|
|
488
|
+
|
|
489
|
+
Args:
|
|
490
|
+
text: the contents of a QMLTP problem file — one or more
|
|
491
|
+
``qmf(...)`` statements (``%`` line comments are ignored by the
|
|
492
|
+
grammar; the header scan below reads them independently).
|
|
493
|
+
|
|
494
|
+
Returns:
|
|
495
|
+
A :class:`QmltpProblem` with ``formulas`` (in source order) and
|
|
496
|
+
``header``.
|
|
497
|
+
|
|
498
|
+
Raises:
|
|
499
|
+
ParsingError: ``text`` is not a well-formed QMLTP problem.
|
|
500
|
+
NotImplementedError: ``text`` uses a construct outside this
|
|
501
|
+
reader's supported fragment (a ``tpi(...)`` multi-modal
|
|
502
|
+
directive, or an indexed ``#box(...)``/``#dia(...)``
|
|
503
|
+
connective) — see module docstring 'Scope'.
|
|
504
|
+
"""
|
|
505
|
+
if _TPI_STATEMENT_RE.search(text):
|
|
506
|
+
raise NotImplementedError(
|
|
507
|
+
"qmltp_input: 'tpi(...)' multi-modal set_logic directives (the "
|
|
508
|
+
"MML domain) are outside this reader's supported mono-modal "
|
|
509
|
+
"fragment -- see module docstring 'Scope'.")
|
|
510
|
+
items = _parse(text, _FILE_PARSER, "problem")
|
|
511
|
+
return QmltpProblem(formulas=tuple(items), header=_parse_qmltp_header(text))
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
def load_qmltp(path: str) -> QmltpProblem:
|
|
515
|
+
"""Read a QMLTP problem file and :func:`parse_qmltp` its contents.
|
|
516
|
+
|
|
517
|
+
Args:
|
|
518
|
+
path: path to a ``.p`` QMLTP problem file, read as UTF-8.
|
|
519
|
+
|
|
520
|
+
Returns:
|
|
521
|
+
A :class:`QmltpProblem`.
|
|
522
|
+
"""
|
|
523
|
+
with open(path, "r", encoding="utf-8") as handle:
|
|
524
|
+
text = handle.read()
|
|
525
|
+
return parse_qmltp(text)
|