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,929 @@
|
|
|
1
|
+
r"""CASL text parsing for classical FOL and many-sorted FOL (MSFOL) — the
|
|
2
|
+
importer half of the export/import pair completed by
|
|
3
|
+
:mod:`unicode_logic_kit.fol.casl_export`.
|
|
4
|
+
|
|
5
|
+
:func:`parse_casl_spec` turns ``spec <NAME> = ... end`` CASL text back into a
|
|
6
|
+
:class:`CaslSpec` (a declared :class:`~unicode_logic_kit.fol.signature.Signature`
|
|
7
|
+
plus the axiom/conjecture ASTs) — a hand-rolled recursive-descent parser, not
|
|
8
|
+
a Lark grammar, since this module owns no ``.lark`` file and is scoped to
|
|
9
|
+
exactly the CASL fragment :func:`~unicode_logic_kit.fol.casl_export.to_casl_spec`
|
|
10
|
+
emits (plus a small, explicitly bounded tolerant superset — see "Scope"
|
|
11
|
+
below), not general CASL. Anything outside that fragment is refused loudly
|
|
12
|
+
with :class:`CaslImportError` naming the offending construct and the line it
|
|
13
|
+
was found on — never silently dropped or mistranslated, the same honesty
|
|
14
|
+
convention :mod:`~unicode_logic_kit.fol.casl_export`'s own docstring states for
|
|
15
|
+
the export direction.
|
|
16
|
+
|
|
17
|
+
Scope: exactly ``to_casl_spec``'s emission, plus a bounded tolerant superset
|
|
18
|
+
--------------------------------------------------------------------------
|
|
19
|
+
SUPPORTED (byte-for-byte what :func:`~unicode_logic_kit.fol.casl_export.to_casl_spec`
|
|
20
|
+
emits, plus the listed tolerances):
|
|
21
|
+
|
|
22
|
+
* One flat ``spec <NAME> = <items> end`` block. ``end`` is OPTIONAL (bare
|
|
23
|
+
end-of-input also terminates the item list — CASL's own basic-spec grammar
|
|
24
|
+
makes the trailing ``end`` optional; this parser follows that rather than
|
|
25
|
+
being stricter than CASL itself).
|
|
26
|
+
* ``sort``/``sorts`` (singular and plural both accepted as synonyms),
|
|
27
|
+
comma-separated sort names.
|
|
28
|
+
* ``op``/``ops`` and ``pred``/``preds`` (singular and plural both accepted),
|
|
29
|
+
semicolon-separated entries. An operation entry is ``name : S1 * … -> R``
|
|
30
|
+
(a function, arity = number of argument sorts) or a bare ``name : S`` (a
|
|
31
|
+
0-ary operation — see "0-ary operations import as Constant" below). A
|
|
32
|
+
predicate entry is ``name : S1 * … * Sn`` or ``name : ()`` for 0-ary.
|
|
33
|
+
* Formulas: ``not``, ``/\``, ``\/``, ``=>``, ``<=>``, ``=``, ``forall``/
|
|
34
|
+
``exists`` with ``x : Sort . <body>``, parentheses, nested quantifiers.
|
|
35
|
+
Precedence (tolerant extension: ``to_casl_spec`` itself always emits full
|
|
36
|
+
parenthesisation, so nothing in ``to_casl_spec`` output actually exercises
|
|
37
|
+
precedence, but hand-written or other tools' CASL text might): ``not`` >
|
|
38
|
+
``/\`` (left-assoc) > ``\/`` (left-assoc) > ``=>`` (right-assoc) > ``<=>``
|
|
39
|
+
(left-assoc chain) — matching ``to_casl_spec``'s own documented reading
|
|
40
|
+
wherever both are defined; verified directly against every golden case in
|
|
41
|
+
``tests/test_casl_export.py``.
|
|
42
|
+
* Multiple variables per quantifier, one shared sort: ``forall x, y : S .
|
|
43
|
+
phi`` desugars to nested same-type/same-sort quantifiers, innermost
|
|
44
|
+
wrapping the body (``forall x : S . forall y : S . phi``) — a tolerant
|
|
45
|
+
extension beyond ``to_casl_spec``, which only ever emits one variable per
|
|
46
|
+
quantifier, but a direct, unambiguous reading of CASL's own VAR-DECL
|
|
47
|
+
grammar (a comma-separated variable list sharing one sort).
|
|
48
|
+
* ``%%`` line comments and the ``%implied`` conjecture annotation, both
|
|
49
|
+
anywhere whitespace-flexible; arbitrary whitespace/newlines everywhere.
|
|
50
|
+
|
|
51
|
+
* Subsorting (``sort S < T``, and the list form ``sort S1, S2 < T``): a
|
|
52
|
+
DIRECT edge, subset-semantics only (``ext(S) ⊆ ext(T)``) — see
|
|
53
|
+
:mod:`unicode_logic_kit.fol.signature`'s "Subsorting" docstring section for
|
|
54
|
+
the exact semantics and why it is deliberately narrower than full CASL
|
|
55
|
+
order-sorted algebra. Parsed into the returned :class:`CaslSpec`'s
|
|
56
|
+
:attr:`~unicode_logic_kit.fol.signature.Signature.subsorts`; a cycle across
|
|
57
|
+
one or more ``sort ... < ...`` declarations is refused with
|
|
58
|
+
:class:`CaslImportError` naming it (the same check
|
|
59
|
+
:class:`~unicode_logic_kit.fol.signature.Signature`'s own ``__post_init__``
|
|
60
|
+
runs, re-wrapped here so every refusal from this module is a
|
|
61
|
+
``CaslImportError``).
|
|
62
|
+
|
|
63
|
+
REFUSED with :class:`CaslImportError` (a specific line number and construct
|
|
64
|
+
name in every message — never silently accepted as if it meant something
|
|
65
|
+
narrower): partial function arrows (``->?``), free/generated types (``free
|
|
66
|
+
type``/``generated type``/bare ``type``/``types`` items), structured-
|
|
67
|
+
specification constructs inside the one spec body (``then``, ``view``,
|
|
68
|
+
``given``, ``arch``, ``unit``, …), and operation/predicate attributes
|
|
69
|
+
(``, assoc``, ``, comm``, …) — these still hold with or without a subsort
|
|
70
|
+
declaration in the same spec, since honoring them would need the full
|
|
71
|
+
order-sorted algebra this module deliberately does not attempt (injections,
|
|
72
|
+
casts, overload resolution across a sort hierarchy — see the "Subsorting"
|
|
73
|
+
bullet above). None of these can ever appear in ``to_casl_spec`` output, so
|
|
74
|
+
refusing them costs nothing against the round-trip contract below and keeps
|
|
75
|
+
this parser from silently accepting CASL it cannot faithfully turn back into
|
|
76
|
+
a kit AST. Operation/predicate OVERLOADING (the same name declared twice
|
|
77
|
+
with a different arity/argument-sort profile, subsort-related sorts or not)
|
|
78
|
+
is likewise still refused — not by a subsort-specific check, but because
|
|
79
|
+
:func:`_declare_function`/:func:`_declare_predicate` already refuse ANY
|
|
80
|
+
redeclaration with a conflicting type, subsorting or not.
|
|
81
|
+
|
|
82
|
+
0-ary operations import as :class:`~unicode_logic_kit.fol.nodes.Constant`, never
|
|
83
|
+
:class:`~unicode_logic_kit.fol.nodes.SortedConstant` — an inherent, documented
|
|
84
|
+
information loss
|
|
85
|
+
----------------------------------------------------------------------------
|
|
86
|
+
``to_casl_spec`` renders a 0-ary operation's ``ops`` entry as a bare
|
|
87
|
+
``name : Sort`` — INDISTINGUISHABLE, by design, from how it would render if
|
|
88
|
+
that name were instead a 0-ary :class:`~unicode_logic_kit.fol.nodes.Function`
|
|
89
|
+
(see :func:`~unicode_logic_kit.fol.casl_export._render_op_type`'s own
|
|
90
|
+
docstring: "indistinguishable in shape from a constant's own type"). And
|
|
91
|
+
every TERM reference to that name — whether the original kit AST built it as
|
|
92
|
+
a plain (unsorted) :class:`Constant`, an explicitly-sorted
|
|
93
|
+
:class:`SortedConstant`, or (in principle) a 0-ary
|
|
94
|
+
:class:`~unicode_logic_kit.fol.nodes.Function` — renders as the exact same bare
|
|
95
|
+
identifier (:func:`~unicode_logic_kit.fol.casl_export._render_term`). So CASL
|
|
96
|
+
text carries no information distinguishing any of these three kit-AST shapes
|
|
97
|
+
for a 0-ary symbol: the ``ops`` declaration only ever states the RESOLVED
|
|
98
|
+
sort (which may have been inferred purely through union-find propagation
|
|
99
|
+
from some OTHER axiom entirely, with no explicit annotation anywhere near
|
|
100
|
+
this occurrence — see ``casl_export``'s own worked example,
|
|
101
|
+
``test_golden_person_animal_msfol_worked_example``, where an unsorted
|
|
102
|
+
``Constant("socrates")`` gets ``ops socrates : Person`` purely from
|
|
103
|
+
appearing in the same predicate argument position as a ``Person``-sorted
|
|
104
|
+
variable in a DIFFERENT axiom).
|
|
105
|
+
|
|
106
|
+
Given that, this parser makes ONE deliberate, uniform choice: every bare
|
|
107
|
+
0-ary term reference resolves to a plain :class:`Constant` — never
|
|
108
|
+
:class:`SortedConstant`, never a 0-ary :class:`Function`. This is not a
|
|
109
|
+
guess dressed up as a default; it is the only choice that does not invent
|
|
110
|
+
information the text does not carry, and it is exactly what makes the
|
|
111
|
+
round-trip contract below hold for ``to_casl_spec``'s own worked example
|
|
112
|
+
(where the original constant WAS a bare, unsorted ``Constant`` despite its
|
|
113
|
+
inferred CASL sort not equal ``default_sort``). A caller whose original
|
|
114
|
+
formulas used :class:`SortedConstant` throughout will NOT get
|
|
115
|
+
byte-identical structural equality back through
|
|
116
|
+
``parse_casl_spec(to_casl_spec(f))`` — this is the one place the export/
|
|
117
|
+
import pair is lossy, and it is lossy on the EXPORT side (the CASL text
|
|
118
|
+
itself has already thrown the distinction away by the time this parser ever
|
|
119
|
+
sees it), not something this parser could recover with a cleverer grammar.
|
|
120
|
+
|
|
121
|
+
The round-trip contract
|
|
122
|
+
------------------------
|
|
123
|
+
For a list ``fs`` of the kit's own classical/many-sorted formulas built with
|
|
124
|
+
plain :class:`~unicode_logic_kit.fol.nodes.Constant` (never
|
|
125
|
+
:class:`SortedConstant`) and without :class:`~unicode_logic_kit.fol.nodes.Xor`
|
|
126
|
+
(see below)::
|
|
127
|
+
|
|
128
|
+
parse_casl_spec(to_casl_spec(fs, conjectures=cjs)).axioms == tuple(fs)
|
|
129
|
+
parse_casl_spec(to_casl_spec(fs, conjectures=cjs)).conjectures == tuple(cjs)
|
|
130
|
+
|
|
131
|
+
holds by structural equality (the kit's node dataclasses are frozen with
|
|
132
|
+
value equality) when no bound variable of ``fs`` or ``cjs`` is spelled like a
|
|
133
|
+
symbol of the spec (a constant, function, predicate or sort). Where one is,
|
|
134
|
+
``to_casl_spec`` writes that binder under a fresh name (CASL has one name
|
|
135
|
+
space for a variable and an operation, see ``casl_export``), and the formulas
|
|
136
|
+
read back equal up to the names of those renamed binders.
|
|
137
|
+
``tests/test_casl_import.py`` hand-verifies the contract over a
|
|
138
|
+
representative formula set covering both quantifier kinds, equality, every
|
|
139
|
+
supported connective, function terms, 0-ary predicates/operations, and
|
|
140
|
+
``%implied``.
|
|
141
|
+
|
|
142
|
+
:class:`~unicode_logic_kit.fol.nodes.Xor` is EXCLUDED from that contract for a
|
|
143
|
+
reason internal to the exporter, not this module: ``to_casl_spec`` has no
|
|
144
|
+
CASL operator for XOR and expands it to the classically-equivalent
|
|
145
|
+
``not (<a> <=> <b>)`` (see ``casl_export``'s own docstring). Parsing that
|
|
146
|
+
text back necessarily yields ``Not(Iff(a, b))``, not ``Xor(a, b)`` — a
|
|
147
|
+
different (semantically equivalent, structurally distinct) tree. This parser
|
|
148
|
+
is faithful to the TEXT it is given: ``not (P <=> Q)`` always means
|
|
149
|
+
``Not(Iff(P, Q))``, exactly like every other ``not``/``<=>`` occurrence,
|
|
150
|
+
never a special-cased reconstruction of ``Xor`` that no other tool
|
|
151
|
+
generating CASL text would expect.
|
|
152
|
+
|
|
153
|
+
A THIRD disclosed exception, exactly analogous to the 0-ary
|
|
154
|
+
Constant-vs-SortedConstant case above (adversarial review, Tier 3): a
|
|
155
|
+
:class:`~unicode_logic_kit.fol.nodes.SortedQuantifier` whose explicit ``sort``
|
|
156
|
+
EQUALS ``default_sort`` renders the very same CASL text as a plain
|
|
157
|
+
:class:`~unicode_logic_kit.fol.nodes.Quantifier` (both emit
|
|
158
|
+
``forall x : <default_sort> . <body>``), so the wire format has already
|
|
159
|
+
thrown the node-type distinction away and this parser's reconstruction rule
|
|
160
|
+
(``sort == default_sort`` → plain ``Quantifier``) necessarily collapses it.
|
|
161
|
+
``∀x:Thing P(x)`` therefore round-trips to the semantically identical but
|
|
162
|
+
structurally distinct ``∀x P(x)`` under the default ``default_sort="Thing"``
|
|
163
|
+
— pinned by ``test_round_trip_sorted_quantifier_at_default_sort_collapses``.
|
|
164
|
+
|
|
165
|
+
Reserved words
|
|
166
|
+
---------------
|
|
167
|
+
Every identifier this parser reads into a declaration OR a reference
|
|
168
|
+
(sort/predicate/operation/variable/spec name) is checked against the same
|
|
169
|
+
CASL keyword list :mod:`~unicode_logic_kit.fol.casl_export` refuses on export
|
|
170
|
+
(``_CASL_KEYWORDS`` there). This is an intentional small independent literal
|
|
171
|
+
copy, not an import of that module's private name — the same reasoning
|
|
172
|
+
:mod:`unicode_logic_kit.fol.signature`'s own docstring gives for its identical
|
|
173
|
+
choice about ``_BUILTIN_PREDS``/``_BUILTIN_FUNCS``: importing a leading-
|
|
174
|
+
underscore name across modules would make this parser depend on
|
|
175
|
+
``casl_export``'s internals staying stable, when the two lists just happen
|
|
176
|
+
to need to agree by CONTENT, not by object identity.
|
|
177
|
+
|
|
178
|
+
The two keywords ``true`` and ``false`` are not refused where a formula is expected:
|
|
179
|
+
they are CASL's own atomic formulas and read as the truth constants, the nullary atoms
|
|
180
|
+
``$true`` and ``$false`` (which :func:`~unicode_logic_kit.fol.casl_export.to_casl_spec`
|
|
181
|
+
writes as ``true`` and ``false``, so a spec the exporter writes reads back). They
|
|
182
|
+
declare no predicate, and as a NAME (a sort, a predicate, an operation or a variable
|
|
183
|
+
called ``true``) they stay refused.
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
from collections import namedtuple
|
|
187
|
+
from dataclasses import dataclass
|
|
188
|
+
from typing import Dict, List, Optional, Sequence, Tuple
|
|
189
|
+
import re
|
|
190
|
+
|
|
191
|
+
from .nodes import (
|
|
192
|
+
Node, Variable, Constant, Function,
|
|
193
|
+
Atom, Not, And, Or, Implies, Iff,
|
|
194
|
+
Quantifier, SortedQuantifier,
|
|
195
|
+
)
|
|
196
|
+
from .signature import Signature, PredicateDecl, FunctionDecl, ConstantDecl
|
|
197
|
+
from ._truth_constants import is_truth_constant
|
|
198
|
+
|
|
199
|
+
__all__ = ["parse_casl_spec", "CaslSpec", "CaslImportError"]
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class CaslImportError(ValueError):
|
|
203
|
+
"""A CASL text construct is outside :func:`parse_casl_spec`'s supported
|
|
204
|
+
grammar, or the text is otherwise malformed.
|
|
205
|
+
|
|
206
|
+
Every raise site names the specific construct and where it sits — a
|
|
207
|
+
``line N:`` prefix for lexical/grammatical refusals, or the axiom/
|
|
208
|
+
conjecture position for the post-parse usage-conformance pass (see
|
|
209
|
+
:func:`_validate_usage` — by then the text positions are gone but the
|
|
210
|
+
formula order is stable). This is the import-side half of the kit's
|
|
211
|
+
"refuse loudly, never silently mistranslate" convention (see
|
|
212
|
+
:mod:`unicode_logic_kit.fol.casl_export`'s module docstring for the same
|
|
213
|
+
principle stated for export).
|
|
214
|
+
"""
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
@dataclass(frozen=True)
|
|
218
|
+
class CaslSpec:
|
|
219
|
+
"""One parsed CASL ``spec`` block: its name, declared vocabulary, and
|
|
220
|
+
formulas, split into axioms and ``%implied`` conjectures.
|
|
221
|
+
|
|
222
|
+
``signature`` is built directly from the ``sorts``/``ops``/``preds``
|
|
223
|
+
declarations actually present in the text (never re-inferred from the
|
|
224
|
+
formula ASTs the way :meth:`~unicode_logic_kit.fol.signature.Signature.from_formulas`
|
|
225
|
+
would) — CASL text already states arities and argument/result sorts
|
|
226
|
+
explicitly, so re-inferring would either duplicate that information or
|
|
227
|
+
risk silently disagreeing with it.
|
|
228
|
+
"""
|
|
229
|
+
|
|
230
|
+
name: str
|
|
231
|
+
signature: Signature
|
|
232
|
+
axioms: Tuple[Node, ...]
|
|
233
|
+
conjectures: Tuple[Node, ...]
|
|
234
|
+
|
|
235
|
+
def to_dict(self) -> dict:
|
|
236
|
+
"""JSON-compatible dict: ``name``, ``signature`` (via
|
|
237
|
+
:meth:`~unicode_logic_kit.fol.signature.Signature.to_dict`), and
|
|
238
|
+
``axioms``/``conjectures`` as lists of node dicts (via each node's
|
|
239
|
+
own ``to_dict``)."""
|
|
240
|
+
return {
|
|
241
|
+
"name": self.name,
|
|
242
|
+
"signature": self.signature.to_dict(),
|
|
243
|
+
"axioms": [a.to_dict() for a in self.axioms],
|
|
244
|
+
"conjectures": [c.to_dict() for c in self.conjectures],
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
# =============================================================================
|
|
249
|
+
# Reserved words (see module docstring's "Reserved words" section)
|
|
250
|
+
# =============================================================================
|
|
251
|
+
|
|
252
|
+
_CASL_KEYWORDS = frozenset({
|
|
253
|
+
"and", "arch", "as", "assoc", "axiom", "axioms", "closed", "comm", "def",
|
|
254
|
+
"else", "end", "exists", "false", "fit", "forall", "free", "from",
|
|
255
|
+
"generated", "get", "given", "hide", "idem", "if", "in", "lambda",
|
|
256
|
+
"library", "local", "logic", "not", "op", "ops", "pred", "preds",
|
|
257
|
+
"result", "reveal", "sort", "sorts", "spec", "then", "to", "true",
|
|
258
|
+
"type", "types", "unit", "units", "var", "vars", "version", "view",
|
|
259
|
+
"when", "with", "within",
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
#: Item-position keywords naming a refused structuring construct: any of
|
|
263
|
+
#: these appearing where a sort/op/pred declaration or an axiom is expected
|
|
264
|
+
#: means the input is a STRUCTURED spec (or a library-level item), which is
|
|
265
|
+
#: out of scope — see the module docstring's "Scope" section.
|
|
266
|
+
_STRUCTURING_KEYWORDS = frozenset({
|
|
267
|
+
"then", "view", "given", "arch", "unit", "units", "hide", "reveal",
|
|
268
|
+
"local", "within", "closed", "library", "logic", "and", "fit",
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
#: Item-position keywords naming a refused free/generated-type construct.
|
|
272
|
+
_FREE_TYPE_KEYWORDS = frozenset({"free", "type", "types", "generated"})
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
# =============================================================================
|
|
276
|
+
# Tokenizer
|
|
277
|
+
# =============================================================================
|
|
278
|
+
|
|
279
|
+
_Token = namedtuple("_Token", "kind value line")
|
|
280
|
+
|
|
281
|
+
# Order matters: alternation tries each pattern in LISTED order at a given
|
|
282
|
+
# position (Python `re` alternation is first-match, not longest-match), so
|
|
283
|
+
# every prefix relationship below is resolved by listing the LONGER/more
|
|
284
|
+
# specific pattern first: ARROW_PARTIAL before ARROW (both start with '->'),
|
|
285
|
+
# IFF before LT (both start with '<'), IMPLIES before EQ (both start with
|
|
286
|
+
# '='), COMMENT before PERCENT_WORD (both start with '%').
|
|
287
|
+
_TOKEN_SPEC = [
|
|
288
|
+
("COMMENT", r"%%[^\r\n]*"),
|
|
289
|
+
("PERCENT_WORD", r"%\S*"),
|
|
290
|
+
("NEWLINE", r"\r\n?|\n"),
|
|
291
|
+
("WS", r"[ \t]+"),
|
|
292
|
+
("ARROW_PARTIAL", r"->\?"),
|
|
293
|
+
("ARROW", r"->"),
|
|
294
|
+
("IFF", r"<=>"),
|
|
295
|
+
("IMPLIES", r"=>"),
|
|
296
|
+
("AND", r"/\\"),
|
|
297
|
+
("OR", r"\\/"),
|
|
298
|
+
("LPAREN", r"\("),
|
|
299
|
+
("RPAREN", r"\)"),
|
|
300
|
+
("COMMA", r","),
|
|
301
|
+
("SEMI", r";"),
|
|
302
|
+
("COLON", r":"),
|
|
303
|
+
("DOT", r"\."),
|
|
304
|
+
("STAR", r"\*"),
|
|
305
|
+
("LT", r"<"),
|
|
306
|
+
("EQ", r"="),
|
|
307
|
+
("IDENT", r"[A-Za-z][A-Za-z0-9_]*"),
|
|
308
|
+
]
|
|
309
|
+
_MASTER_RE = re.compile("|".join(f"(?P<{name}>{pat})" for name, pat in _TOKEN_SPEC))
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
class _Lexer:
|
|
313
|
+
"""Scans ``text`` into tokens ON DEMAND, buffering only as much
|
|
314
|
+
lookahead as :class:`_Parser` actually asks for (via :meth:`peek`/
|
|
315
|
+
:meth:`next`), rather than tokenizing the whole input up front.
|
|
316
|
+
|
|
317
|
+
This matters for refusal quality, not just laziness for its own sake:
|
|
318
|
+
an out-of-scope construct this parser means to refuse by NAME (e.g. a
|
|
319
|
+
``free type ... ::= zero | succ(Nat)`` declaration) may contain
|
|
320
|
+
characters this grammar has no token for at all (``|``) FURTHER ALONG
|
|
321
|
+
in the same construct. Eager whole-text tokenization would hit that
|
|
322
|
+
unrecognized character first and raise a generic "unrecognized
|
|
323
|
+
character" error before the parser ever gets a chance to recognize
|
|
324
|
+
``free`` at item-position and give its specific, named refusal. Lazy
|
|
325
|
+
scanning means the parser sees (and can react to) the ``free`` token
|
|
326
|
+
the moment it asks for the next one, without this lexer ever having
|
|
327
|
+
attempted to scan past it into the unsupported tail of the construct.
|
|
328
|
+
|
|
329
|
+
Comments (``%%...`` to end of line) and whitespace/newlines are
|
|
330
|
+
consumed silently while scanning — callers never see them as tokens. A
|
|
331
|
+
bare ``%word`` other than ``%implied`` is refused immediately (a CASL
|
|
332
|
+
annotation this parser does not support, e.g. ``%(label)%`` or
|
|
333
|
+
``%def`` — none of these are ever emitted by ``to_casl_spec``).
|
|
334
|
+
"""
|
|
335
|
+
|
|
336
|
+
def __init__(self, text: str):
|
|
337
|
+
self._text = text
|
|
338
|
+
self._pos = 0
|
|
339
|
+
self._n = len(text)
|
|
340
|
+
self._line = 1
|
|
341
|
+
self._buffer: List[_Token] = []
|
|
342
|
+
|
|
343
|
+
def _scan_one(self) -> _Token:
|
|
344
|
+
while self._pos < self._n:
|
|
345
|
+
m = _MASTER_RE.match(self._text, self._pos)
|
|
346
|
+
if not m:
|
|
347
|
+
raise CaslImportError(
|
|
348
|
+
f"line {self._line}: unrecognized character "
|
|
349
|
+
f"{self._text[self._pos]!r} in CASL text"
|
|
350
|
+
)
|
|
351
|
+
kind, value = m.lastgroup, m.group()
|
|
352
|
+
if kind == "NEWLINE":
|
|
353
|
+
self._line += 1
|
|
354
|
+
self._pos = m.end()
|
|
355
|
+
continue
|
|
356
|
+
if kind in ("WS", "COMMENT"):
|
|
357
|
+
self._pos = m.end()
|
|
358
|
+
continue
|
|
359
|
+
if kind == "PERCENT_WORD":
|
|
360
|
+
if value != "%implied":
|
|
361
|
+
raise CaslImportError(
|
|
362
|
+
f"line {self._line}: unsupported CASL annotation "
|
|
363
|
+
f"{value!r} — only '%implied' (conjecture marker) "
|
|
364
|
+
"and '%%' line comments are supported"
|
|
365
|
+
)
|
|
366
|
+
kind = "IMPLIED"
|
|
367
|
+
self._pos = m.end()
|
|
368
|
+
return _Token(kind, value, self._line)
|
|
369
|
+
return _Token("EOF", "", self._line)
|
|
370
|
+
|
|
371
|
+
def peek(self, ahead: int = 0) -> _Token:
|
|
372
|
+
"""Return the token ``ahead`` positions past the cursor without
|
|
373
|
+
consuming it (``ahead=0`` is the next token to be consumed)."""
|
|
374
|
+
while len(self._buffer) <= ahead:
|
|
375
|
+
self._buffer.append(self._scan_one())
|
|
376
|
+
return self._buffer[ahead]
|
|
377
|
+
|
|
378
|
+
def next(self) -> _Token:
|
|
379
|
+
"""Consume and return the next token."""
|
|
380
|
+
self.peek(0)
|
|
381
|
+
return self._buffer.pop(0)
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
# =============================================================================
|
|
385
|
+
# Recursive-descent parser
|
|
386
|
+
# =============================================================================
|
|
387
|
+
|
|
388
|
+
class _Parser:
|
|
389
|
+
"""Consumes a token list once, left to right, building spec-level
|
|
390
|
+
declarations and formula ASTs. See the module docstring's "Scope"
|
|
391
|
+
section for the exact grammar and the refusal classes."""
|
|
392
|
+
|
|
393
|
+
def __init__(self, lexer: "_Lexer", default_sort: str):
|
|
394
|
+
self._lexer = lexer
|
|
395
|
+
self.default_sort = default_sort
|
|
396
|
+
self.sorts: set = set()
|
|
397
|
+
#: child sort name -> the set of DIRECT parent sort names declared
|
|
398
|
+
#: for it (accumulated across every ``sort ... < ...`` decl item —
|
|
399
|
+
#: a child can gain further parents from a LATER decl in the same
|
|
400
|
+
#: spec, e.g. multiple inheritance spread across two lines).
|
|
401
|
+
self.subsorts: Dict[str, set] = {}
|
|
402
|
+
self.predicates: Dict[str, PredicateDecl] = {}
|
|
403
|
+
self.functions: Dict[str, FunctionDecl] = {}
|
|
404
|
+
self.constants: Dict[str, ConstantDecl] = {}
|
|
405
|
+
self.axioms: List[Node] = []
|
|
406
|
+
self.conjectures: List[Node] = []
|
|
407
|
+
|
|
408
|
+
# -- token helpers --------------------------------------------------
|
|
409
|
+
|
|
410
|
+
def _cur(self) -> _Token:
|
|
411
|
+
return self._lexer.peek(0)
|
|
412
|
+
|
|
413
|
+
def _at(self, kind: str) -> bool:
|
|
414
|
+
return self._cur().kind == kind
|
|
415
|
+
|
|
416
|
+
def _at_ident(self, *values: str) -> bool:
|
|
417
|
+
t = self._cur()
|
|
418
|
+
return t.kind == "IDENT" and t.value in values
|
|
419
|
+
|
|
420
|
+
def _advance(self) -> _Token:
|
|
421
|
+
return self._lexer.next()
|
|
422
|
+
|
|
423
|
+
def _expect(self, kind: str, what: str) -> _Token:
|
|
424
|
+
if not self._at(kind):
|
|
425
|
+
self._error(f"expected {what}, found {self._describe(self._cur())}")
|
|
426
|
+
return self._advance()
|
|
427
|
+
|
|
428
|
+
def _expect_ident(self, *values: str) -> _Token:
|
|
429
|
+
if not self._at_ident(*values):
|
|
430
|
+
self._error(
|
|
431
|
+
f"expected one of {sorted(values)!r}, found "
|
|
432
|
+
f"{self._describe(self._cur())}"
|
|
433
|
+
)
|
|
434
|
+
return self._advance()
|
|
435
|
+
|
|
436
|
+
@staticmethod
|
|
437
|
+
def _describe(t: _Token) -> str:
|
|
438
|
+
return f"{t.kind} {t.value!r}" if t.value else t.kind
|
|
439
|
+
|
|
440
|
+
def _error(self, msg: str):
|
|
441
|
+
raise CaslImportError(f"line {self._cur().line}: {msg}")
|
|
442
|
+
|
|
443
|
+
def _check_reserved(self, name: str, kind: str, line: int) -> None:
|
|
444
|
+
if name in _CASL_KEYWORDS:
|
|
445
|
+
raise CaslImportError(
|
|
446
|
+
f"line {line}: {kind} name '{name}' collides with the "
|
|
447
|
+
f"reserved CASL keyword '{name}'"
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
# -- top level --------------------------------------------------------
|
|
451
|
+
|
|
452
|
+
def parse(self) -> str:
|
|
453
|
+
"""Parse ``spec NAME = <items> end`` (``end`` optional); return NAME."""
|
|
454
|
+
self._expect_ident("spec")
|
|
455
|
+
name_tok = self._expect("IDENT", "spec name")
|
|
456
|
+
self._check_reserved(name_tok.value, "spec name", name_tok.line)
|
|
457
|
+
self._expect("EQ", "'=' after spec name")
|
|
458
|
+
while True:
|
|
459
|
+
if self._at("EOF"):
|
|
460
|
+
break
|
|
461
|
+
if self._at_ident("end"):
|
|
462
|
+
self._advance()
|
|
463
|
+
break
|
|
464
|
+
if self._at("DOT"):
|
|
465
|
+
self._parse_axiom_line()
|
|
466
|
+
continue
|
|
467
|
+
if self._at_ident("sort", "sorts"):
|
|
468
|
+
self._parse_sort_decl()
|
|
469
|
+
continue
|
|
470
|
+
if self._at_ident("op", "ops"):
|
|
471
|
+
self._parse_op_decl()
|
|
472
|
+
continue
|
|
473
|
+
if self._at_ident("pred", "preds"):
|
|
474
|
+
self._parse_pred_decl()
|
|
475
|
+
continue
|
|
476
|
+
if self._at_ident(*_FREE_TYPE_KEYWORDS):
|
|
477
|
+
self._error(
|
|
478
|
+
f"free/generated type declaration ({self._cur().value!r}) "
|
|
479
|
+
"is outside parse_casl_spec's supported grammar"
|
|
480
|
+
)
|
|
481
|
+
if self._at_ident(*_STRUCTURING_KEYWORDS):
|
|
482
|
+
self._error(
|
|
483
|
+
f"structured-specification construct "
|
|
484
|
+
f"{self._cur().value!r} is outside parse_casl_spec's "
|
|
485
|
+
"supported grammar — only a single flat "
|
|
486
|
+
"'spec NAME = ... end' block is supported"
|
|
487
|
+
)
|
|
488
|
+
if self._at("IDENT"):
|
|
489
|
+
nxt = self._lexer.peek(1)
|
|
490
|
+
if nxt.kind == "IDENT" and nxt.value == "then":
|
|
491
|
+
self._error(
|
|
492
|
+
"structured-specification construct 'then' (spec "
|
|
493
|
+
f"extension, e.g. 'spec X = {self._cur().value} "
|
|
494
|
+
"then ...') is outside parse_casl_spec's supported "
|
|
495
|
+
"grammar — only a single flat 'spec NAME = ... end' "
|
|
496
|
+
"block is supported (see unicode_logic_kit.hets.dol "
|
|
497
|
+
"for DOL library 'extends' emission)"
|
|
498
|
+
)
|
|
499
|
+
self._error(
|
|
500
|
+
f"unexpected {self._describe(self._cur())} where a sort/op/"
|
|
501
|
+
"pred declaration or an axiom ('.') was expected"
|
|
502
|
+
)
|
|
503
|
+
if not self._at("EOF"):
|
|
504
|
+
self._error("unexpected content after 'end'")
|
|
505
|
+
return name_tok.value
|
|
506
|
+
|
|
507
|
+
# -- declarations -------------------------------------------------------
|
|
508
|
+
|
|
509
|
+
def _check_no_attributes(self) -> None:
|
|
510
|
+
"""Refuse a trailing ``, assoc`` / ``, comm`` / … attribute list —
|
|
511
|
+
the only thing a ',' can legally introduce after an op/pred type in
|
|
512
|
+
this grammar."""
|
|
513
|
+
if self._at("COMMA"):
|
|
514
|
+
self._error(
|
|
515
|
+
"operation/predicate attributes (e.g. ', assoc', ', comm') "
|
|
516
|
+
"are outside parse_casl_spec's supported grammar"
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
def _parse_sort_decl(self) -> None:
|
|
520
|
+
"""Parse one ``sort``/``sorts`` item: a plain comma-separated name
|
|
521
|
+
list (``sort S1, S2``), or a subsort declaration (``sort S1, S2 <
|
|
522
|
+
T``, CASL's ``sort-id-list "<" sort-id`` production — every name in
|
|
523
|
+
the list becomes a DIRECT subsort of ``T``). The ``<`` can only
|
|
524
|
+
follow the WHOLE list, matching CASL's own grammar (not after each
|
|
525
|
+
individual name), so ``sort S < T`` and ``sort S1, S2 < T`` are both
|
|
526
|
+
supported but ``sort S1 < T1, S2 < T2`` (two subsort decls chained
|
|
527
|
+
by comma) is not — write those as two separate ``sort ...`` items.
|
|
528
|
+
"""
|
|
529
|
+
self._advance() # 'sort' | 'sorts'
|
|
530
|
+
names = []
|
|
531
|
+
while True:
|
|
532
|
+
tok = self._expect("IDENT", "sort name")
|
|
533
|
+
self._check_reserved(tok.value, "sort", tok.line)
|
|
534
|
+
names.append(tok.value)
|
|
535
|
+
if self._at("COMMA"):
|
|
536
|
+
self._advance()
|
|
537
|
+
continue
|
|
538
|
+
break
|
|
539
|
+
if self._at("LT"):
|
|
540
|
+
self._advance()
|
|
541
|
+
parent_tok = self._expect("IDENT", "supersort name")
|
|
542
|
+
self._check_reserved(parent_tok.value, "sort", parent_tok.line)
|
|
543
|
+
parent = parent_tok.value
|
|
544
|
+
self.sorts.add(parent)
|
|
545
|
+
for child in names:
|
|
546
|
+
self.sorts.add(child)
|
|
547
|
+
self.subsorts.setdefault(child, set()).add(parent)
|
|
548
|
+
return
|
|
549
|
+
for name in names:
|
|
550
|
+
self.sorts.add(name)
|
|
551
|
+
|
|
552
|
+
def _parse_op_decl(self) -> None:
|
|
553
|
+
self._advance() # 'op' | 'ops'
|
|
554
|
+
while True:
|
|
555
|
+
name_tok = self._expect("IDENT", "operation name")
|
|
556
|
+
name = name_tok.value
|
|
557
|
+
self._check_reserved(name, "operation", name_tok.line)
|
|
558
|
+
self._expect("COLON", "':' after operation name")
|
|
559
|
+
arg_sorts, result_sort = self._parse_op_type()
|
|
560
|
+
self._check_no_attributes()
|
|
561
|
+
if arg_sorts:
|
|
562
|
+
self._declare_function(name, arg_sorts, result_sort, name_tok.line)
|
|
563
|
+
else:
|
|
564
|
+
self._declare_constant(name, result_sort, name_tok.line)
|
|
565
|
+
if self._at("SEMI"):
|
|
566
|
+
self._advance()
|
|
567
|
+
continue
|
|
568
|
+
break
|
|
569
|
+
|
|
570
|
+
def _parse_op_type(self) -> Tuple[List[str], str]:
|
|
571
|
+
if self._at("ARROW_PARTIAL"):
|
|
572
|
+
self._error(
|
|
573
|
+
"partial function arrow ('->?') is outside parse_casl_spec's "
|
|
574
|
+
"supported grammar"
|
|
575
|
+
)
|
|
576
|
+
first = self._expect("IDENT", "sort name")
|
|
577
|
+
self._check_reserved(first.value, "sort", first.line)
|
|
578
|
+
sorts = [first.value]
|
|
579
|
+
while self._at("STAR"):
|
|
580
|
+
self._advance()
|
|
581
|
+
tok = self._expect("IDENT", "sort name")
|
|
582
|
+
self._check_reserved(tok.value, "sort", tok.line)
|
|
583
|
+
sorts.append(tok.value)
|
|
584
|
+
if self._at("ARROW_PARTIAL"):
|
|
585
|
+
self._error(
|
|
586
|
+
"partial function arrow ('->?') is outside parse_casl_spec's "
|
|
587
|
+
"supported grammar"
|
|
588
|
+
)
|
|
589
|
+
if self._at("ARROW"):
|
|
590
|
+
self._advance()
|
|
591
|
+
result = self._expect("IDENT", "result sort name")
|
|
592
|
+
self._check_reserved(result.value, "sort", result.line)
|
|
593
|
+
for s in sorts:
|
|
594
|
+
self.sorts.add(s)
|
|
595
|
+
self.sorts.add(result.value)
|
|
596
|
+
return sorts, result.value
|
|
597
|
+
if len(sorts) != 1:
|
|
598
|
+
self._error(
|
|
599
|
+
"expected '->' after a multi-sort argument list in an "
|
|
600
|
+
"operation type"
|
|
601
|
+
)
|
|
602
|
+
self.sorts.add(sorts[0])
|
|
603
|
+
return [], sorts[0]
|
|
604
|
+
|
|
605
|
+
def _parse_pred_decl(self) -> None:
|
|
606
|
+
self._advance() # 'pred' | 'preds'
|
|
607
|
+
while True:
|
|
608
|
+
name_tok = self._expect("IDENT", "predicate name")
|
|
609
|
+
name = name_tok.value
|
|
610
|
+
self._check_reserved(name, "predicate", name_tok.line)
|
|
611
|
+
self._expect("COLON", "':' after predicate name")
|
|
612
|
+
arg_sorts = self._parse_pred_type()
|
|
613
|
+
self._check_no_attributes()
|
|
614
|
+
self._declare_predicate(name, arg_sorts, name_tok.line)
|
|
615
|
+
if self._at("SEMI"):
|
|
616
|
+
self._advance()
|
|
617
|
+
continue
|
|
618
|
+
break
|
|
619
|
+
|
|
620
|
+
def _parse_pred_type(self) -> List[str]:
|
|
621
|
+
if self._at("LPAREN"):
|
|
622
|
+
self._advance()
|
|
623
|
+
self._expect("RPAREN", "')' closing a 0-ary predicate type")
|
|
624
|
+
return []
|
|
625
|
+
tok = self._expect("IDENT", "sort name")
|
|
626
|
+
self._check_reserved(tok.value, "sort", tok.line)
|
|
627
|
+
sorts = [tok.value]
|
|
628
|
+
self.sorts.add(tok.value)
|
|
629
|
+
while self._at("STAR"):
|
|
630
|
+
self._advance()
|
|
631
|
+
tok = self._expect("IDENT", "sort name")
|
|
632
|
+
self._check_reserved(tok.value, "sort", tok.line)
|
|
633
|
+
sorts.append(tok.value)
|
|
634
|
+
self.sorts.add(tok.value)
|
|
635
|
+
return sorts
|
|
636
|
+
|
|
637
|
+
def _declare_function(self, name: str, arg_sorts: List[str],
|
|
638
|
+
result_sort: str, line: int) -> None:
|
|
639
|
+
if name in self.constants:
|
|
640
|
+
self._error(
|
|
641
|
+
f"'{name}' is declared both as a 0-ary operation and as a "
|
|
642
|
+
"function with arguments — a signature declares at most "
|
|
643
|
+
"one meaning per operation name"
|
|
644
|
+
)
|
|
645
|
+
decl = FunctionDecl(name, len(arg_sorts), tuple(arg_sorts), result_sort)
|
|
646
|
+
if name in self.functions and self.functions[name] != decl:
|
|
647
|
+
self._error(f"operation '{name}' redeclared with a conflicting type")
|
|
648
|
+
self.functions[name] = decl
|
|
649
|
+
|
|
650
|
+
def _declare_constant(self, name: str, sort: str, line: int) -> None:
|
|
651
|
+
if name in self.functions:
|
|
652
|
+
self._error(
|
|
653
|
+
f"'{name}' is declared both as a 0-ary operation and as a "
|
|
654
|
+
"function with arguments — a signature declares at most "
|
|
655
|
+
"one meaning per operation name"
|
|
656
|
+
)
|
|
657
|
+
decl = ConstantDecl(name, sort)
|
|
658
|
+
if name in self.constants and self.constants[name] != decl:
|
|
659
|
+
self._error(f"operation '{name}' redeclared with a conflicting sort")
|
|
660
|
+
self.constants[name] = decl
|
|
661
|
+
|
|
662
|
+
def _declare_predicate(self, name: str, arg_sorts: List[str], line: int) -> None:
|
|
663
|
+
decl = PredicateDecl(name, len(arg_sorts), tuple(arg_sorts) if arg_sorts else None)
|
|
664
|
+
if name in self.predicates and self.predicates[name] != decl:
|
|
665
|
+
self._error(f"predicate '{name}' redeclared with a conflicting type")
|
|
666
|
+
self.predicates[name] = decl
|
|
667
|
+
|
|
668
|
+
# -- axioms / formulas ----------------------------------------------
|
|
669
|
+
|
|
670
|
+
def _parse_axiom_line(self) -> None:
|
|
671
|
+
self._advance() # leading '.'
|
|
672
|
+
formula = self._parse_formula(frozenset())
|
|
673
|
+
if self._at("IMPLIED"):
|
|
674
|
+
self._advance()
|
|
675
|
+
self.conjectures.append(formula)
|
|
676
|
+
else:
|
|
677
|
+
self.axioms.append(formula)
|
|
678
|
+
|
|
679
|
+
def _parse_formula(self, env: frozenset) -> Node:
|
|
680
|
+
return self._parse_iff(env)
|
|
681
|
+
|
|
682
|
+
def _parse_iff(self, env: frozenset) -> Node:
|
|
683
|
+
left = self._parse_implies(env)
|
|
684
|
+
while self._at("IFF"):
|
|
685
|
+
self._advance()
|
|
686
|
+
right = self._parse_implies(env)
|
|
687
|
+
left = Iff(left, right)
|
|
688
|
+
return left
|
|
689
|
+
|
|
690
|
+
def _parse_implies(self, env: frozenset) -> Node:
|
|
691
|
+
left = self._parse_or(env)
|
|
692
|
+
if self._at("IMPLIES"):
|
|
693
|
+
self._advance()
|
|
694
|
+
right = self._parse_implies(env) # right-associative
|
|
695
|
+
return Implies(left, right)
|
|
696
|
+
return left
|
|
697
|
+
|
|
698
|
+
def _parse_or(self, env: frozenset) -> Node:
|
|
699
|
+
left = self._parse_and(env)
|
|
700
|
+
while self._at("OR"):
|
|
701
|
+
self._advance()
|
|
702
|
+
right = self._parse_and(env)
|
|
703
|
+
left = Or(left, right)
|
|
704
|
+
return left
|
|
705
|
+
|
|
706
|
+
def _parse_and(self, env: frozenset) -> Node:
|
|
707
|
+
left = self._parse_unary(env)
|
|
708
|
+
while self._at("AND"):
|
|
709
|
+
self._advance()
|
|
710
|
+
right = self._parse_unary(env)
|
|
711
|
+
left = And(left, right)
|
|
712
|
+
return left
|
|
713
|
+
|
|
714
|
+
def _parse_unary(self, env: frozenset) -> Node:
|
|
715
|
+
if self._at_ident("not"):
|
|
716
|
+
self._advance()
|
|
717
|
+
return Not(self._parse_unary(env))
|
|
718
|
+
return self._parse_primary(env)
|
|
719
|
+
|
|
720
|
+
def _parse_primary(self, env: frozenset) -> Node:
|
|
721
|
+
if self._at_ident("true", "false"):
|
|
722
|
+
# CASL's own atomic formulas: the truth constants.
|
|
723
|
+
word = self._advance().value
|
|
724
|
+
if self._at("LPAREN") or self._at("EQ"):
|
|
725
|
+
self._error(f"'{word}' is a CASL formula, not a predicate or a term: it "
|
|
726
|
+
"takes no arguments and is no operand of '='")
|
|
727
|
+
return Atom("$true" if word == "true" else "$false", ())
|
|
728
|
+
if self._at_ident("forall", "exists"):
|
|
729
|
+
return self._parse_quantifier(env)
|
|
730
|
+
if self._at("LPAREN"):
|
|
731
|
+
self._advance()
|
|
732
|
+
f = self._parse_formula(env)
|
|
733
|
+
self._expect("RPAREN", "')'")
|
|
734
|
+
return f
|
|
735
|
+
return self._parse_atom_or_equality(env)
|
|
736
|
+
|
|
737
|
+
def _parse_quantifier(self, env: frozenset) -> Node:
|
|
738
|
+
kw = self._advance() # 'forall' | 'exists'
|
|
739
|
+
qtype = "∀" if kw.value == "forall" else "∃"
|
|
740
|
+
names = []
|
|
741
|
+
tok = self._expect("IDENT", "quantified variable name")
|
|
742
|
+
self._check_reserved(tok.value, "variable", tok.line)
|
|
743
|
+
names.append(tok.value)
|
|
744
|
+
while self._at("COMMA"):
|
|
745
|
+
self._advance()
|
|
746
|
+
tok = self._expect("IDENT", "quantified variable name")
|
|
747
|
+
self._check_reserved(tok.value, "variable", tok.line)
|
|
748
|
+
names.append(tok.value)
|
|
749
|
+
self._expect("COLON", "':' after quantified variable(s)")
|
|
750
|
+
sort_tok = self._expect("IDENT", "sort name")
|
|
751
|
+
sort = sort_tok.value
|
|
752
|
+
self._check_reserved(sort, "sort", sort_tok.line)
|
|
753
|
+
self.sorts.add(sort)
|
|
754
|
+
self._expect("DOT", "'.' after the quantifier's sort")
|
|
755
|
+
new_env = env | frozenset(names)
|
|
756
|
+
body = self._parse_formula(new_env)
|
|
757
|
+
result = body
|
|
758
|
+
for name in reversed(names):
|
|
759
|
+
var = Variable(name)
|
|
760
|
+
if sort == self.default_sort:
|
|
761
|
+
result = Quantifier(qtype, var, result)
|
|
762
|
+
else:
|
|
763
|
+
result = SortedQuantifier(qtype, var, sort, result)
|
|
764
|
+
return result
|
|
765
|
+
|
|
766
|
+
def _parse_atom_or_equality(self, env: frozenset) -> Node:
|
|
767
|
+
name_tok = self._expect(
|
|
768
|
+
"IDENT", "an atom, predicate application, or term")
|
|
769
|
+
name = name_tok.value
|
|
770
|
+
self._check_reserved(name, "predicate/operation", name_tok.line)
|
|
771
|
+
args = self._parse_optional_arg_list(env)
|
|
772
|
+
if self._at("EQ"):
|
|
773
|
+
left_term = self._resolve_term(name, args, env)
|
|
774
|
+
self._advance() # '='
|
|
775
|
+
right_term = self._parse_term(env)
|
|
776
|
+
return Atom("=", (left_term, right_term))
|
|
777
|
+
return Atom(name, tuple(args))
|
|
778
|
+
|
|
779
|
+
def _parse_term(self, env: frozenset) -> Node:
|
|
780
|
+
tok = self._expect("IDENT", "a term (variable, constant, or function)")
|
|
781
|
+
name = tok.value
|
|
782
|
+
self._check_reserved(name, "predicate/operation", tok.line)
|
|
783
|
+
args = self._parse_optional_arg_list(env)
|
|
784
|
+
return self._resolve_term(name, args, env)
|
|
785
|
+
|
|
786
|
+
def _parse_optional_arg_list(self, env: frozenset) -> List[Node]:
|
|
787
|
+
if not self._at("LPAREN"):
|
|
788
|
+
return []
|
|
789
|
+
self._advance()
|
|
790
|
+
args = [self._parse_term(env)]
|
|
791
|
+
while self._at("COMMA"):
|
|
792
|
+
self._advance()
|
|
793
|
+
args.append(self._parse_term(env))
|
|
794
|
+
self._expect("RPAREN", "')' closing an argument list")
|
|
795
|
+
return args
|
|
796
|
+
|
|
797
|
+
@staticmethod
|
|
798
|
+
def _resolve_term(name: str, args: List[Node], env: frozenset) -> Node:
|
|
799
|
+
"""A bare designator resolves to a bound :class:`Variable` (if
|
|
800
|
+
``name`` is in scope), else a :class:`Constant`; an applied one
|
|
801
|
+
(``args`` non-empty) always resolves to a :class:`Function` — see
|
|
802
|
+
the module docstring's "0-ary operations import as Constant"
|
|
803
|
+
section for why a bare reference never becomes a
|
|
804
|
+
:class:`~unicode_logic_kit.fol.nodes.SortedConstant` or a 0-ary
|
|
805
|
+
:class:`Function`."""
|
|
806
|
+
if args:
|
|
807
|
+
return Function(name, tuple(args))
|
|
808
|
+
if name in env:
|
|
809
|
+
return Variable(name)
|
|
810
|
+
return Constant(name)
|
|
811
|
+
|
|
812
|
+
|
|
813
|
+
# =============================================================================
|
|
814
|
+
# Public API
|
|
815
|
+
# =============================================================================
|
|
816
|
+
|
|
817
|
+
def _validate_usage(parser: "_Parser") -> None:
|
|
818
|
+
"""Post-parse conformance pass: every symbol the formulas USE must have
|
|
819
|
+
been DECLARED, with matching arity.
|
|
820
|
+
|
|
821
|
+
The recursive-descent parser builds Atom/Function/Constant nodes purely
|
|
822
|
+
from the tokens it encounters; without this pass a symbol used but never
|
|
823
|
+
declared silently produced a :class:`CaslSpec` whose signature does not
|
|
824
|
+
describe its own axioms, and a declared/used arity mismatch was accepted
|
|
825
|
+
outright (adversarial review, Tier 3) — both violating the module's
|
|
826
|
+
"refuse loudly" contract. Runs after the whole spec is parsed (a
|
|
827
|
+
declaration is in scope for every formula regardless of textual order),
|
|
828
|
+
so errors carry the axiom/conjecture position instead of a line number.
|
|
829
|
+
"""
|
|
830
|
+
labeled = (
|
|
831
|
+
[(f"axiom {i + 1}", f) for i, f in enumerate(parser.axioms)]
|
|
832
|
+
+ [(f"%implied conjecture {i + 1}", f)
|
|
833
|
+
for i, f in enumerate(parser.conjectures)])
|
|
834
|
+
for label, formula in labeled:
|
|
835
|
+
for node in formula.walk():
|
|
836
|
+
if is_truth_constant(node):
|
|
837
|
+
continue # `true` / `false`: CASL's own formulas, declared by no one
|
|
838
|
+
if isinstance(node, Atom) and node.predicate != "=":
|
|
839
|
+
decl = parser.predicates.get(node.predicate)
|
|
840
|
+
if decl is None:
|
|
841
|
+
raise CaslImportError(
|
|
842
|
+
f"{label}: predicate '{node.predicate}' is used but "
|
|
843
|
+
f"never declared (add a 'preds {node.predicate} : "
|
|
844
|
+
f"...' declaration)")
|
|
845
|
+
if decl.arity != len(node.args):
|
|
846
|
+
raise CaslImportError(
|
|
847
|
+
f"{label}: predicate '{node.predicate}' is declared "
|
|
848
|
+
f"with arity {decl.arity} but used with "
|
|
849
|
+
f"{len(node.args)} argument(s)")
|
|
850
|
+
elif isinstance(node, Function):
|
|
851
|
+
decl = parser.functions.get(node.name)
|
|
852
|
+
if decl is None:
|
|
853
|
+
raise CaslImportError(
|
|
854
|
+
f"{label}: operation '{node.name}' is used but never "
|
|
855
|
+
f"declared (add an 'ops {node.name} : ...' "
|
|
856
|
+
f"declaration)")
|
|
857
|
+
if decl.arity != len(node.args):
|
|
858
|
+
raise CaslImportError(
|
|
859
|
+
f"{label}: operation '{node.name}' is declared with "
|
|
860
|
+
f"arity {decl.arity} but used with "
|
|
861
|
+
f"{len(node.args)} argument(s)")
|
|
862
|
+
elif isinstance(node, Constant):
|
|
863
|
+
if node.name not in parser.constants:
|
|
864
|
+
raise CaslImportError(
|
|
865
|
+
f"{label}: constant '{node.name}' is used but never "
|
|
866
|
+
f"declared (add an 'ops {node.name} : <Sort>' "
|
|
867
|
+
f"declaration)")
|
|
868
|
+
|
|
869
|
+
|
|
870
|
+
def parse_casl_spec(text: str, *, default_sort: str = "Thing") -> CaslSpec:
|
|
871
|
+
"""Parse one CASL ``spec <NAME> = … end`` block into a :class:`CaslSpec`.
|
|
872
|
+
|
|
873
|
+
See the module docstring for the exact grammar supported (a bounded
|
|
874
|
+
tolerant superset of what
|
|
875
|
+
:func:`unicode_logic_kit.fol.casl_export.to_casl_spec` emits), the round-
|
|
876
|
+
trip contract this establishes with that exporter, and the refusal
|
|
877
|
+
classes below.
|
|
878
|
+
|
|
879
|
+
Args:
|
|
880
|
+
text: CASL spec text.
|
|
881
|
+
default_sort: the sort a PLAIN (unsorted)
|
|
882
|
+
:class:`~unicode_logic_kit.fol.nodes.Quantifier` is reconstructed
|
|
883
|
+
for — a quantifier declared at exactly this sort in the text
|
|
884
|
+
becomes a plain ``Quantifier``; any other sort becomes a
|
|
885
|
+
:class:`~unicode_logic_kit.fol.nodes.SortedQuantifier`. Must match
|
|
886
|
+
whatever ``default_sort`` the text was originally exported with
|
|
887
|
+
for the round-trip contract to hold.
|
|
888
|
+
|
|
889
|
+
Returns:
|
|
890
|
+
A :class:`CaslSpec` carrying the spec's name, its declared
|
|
891
|
+
:class:`~unicode_logic_kit.fol.signature.Signature` (read directly off
|
|
892
|
+
the ``sorts``/``subsorts``/``ops``/``preds`` declarations, not
|
|
893
|
+
re-inferred from the formulas), and its axioms/conjectures as kit
|
|
894
|
+
ASTs.
|
|
895
|
+
|
|
896
|
+
Raises:
|
|
897
|
+
CaslImportError: a construct outside the supported grammar (partial
|
|
898
|
+
functions, a subsort cycle, free/generated types, structured-spec
|
|
899
|
+
constructs, operation/predicate attributes, an unrecognized
|
|
900
|
+
token, a malformed op/pred type, a reserved-word identifier, or
|
|
901
|
+
any other parse failure) — always with a line number (or, for a
|
|
902
|
+
subsort cycle — detected only once the whole spec is assembled —
|
|
903
|
+
the cycle itself) named.
|
|
904
|
+
"""
|
|
905
|
+
lexer = _Lexer(text)
|
|
906
|
+
parser = _Parser(lexer, default_sort)
|
|
907
|
+
name = parser.parse()
|
|
908
|
+
_validate_usage(parser)
|
|
909
|
+
try:
|
|
910
|
+
signature = Signature(
|
|
911
|
+
predicates=dict(parser.predicates),
|
|
912
|
+
functions=dict(parser.functions),
|
|
913
|
+
constants=dict(parser.constants),
|
|
914
|
+
sorts=frozenset(parser.sorts),
|
|
915
|
+
subsorts={child: frozenset(parents)
|
|
916
|
+
for child, parents in parser.subsorts.items()},
|
|
917
|
+
)
|
|
918
|
+
except ValueError as e:
|
|
919
|
+
# Signature.__post_init__'s own subsort-cycle check — re-raised as
|
|
920
|
+
# this module's own error type so every refusal from parse_casl_spec
|
|
921
|
+
# is a CaslImportError, never a bare ValueError leaking a different
|
|
922
|
+
# module's exception type.
|
|
923
|
+
raise CaslImportError(str(e)) from e
|
|
924
|
+
return CaslSpec(
|
|
925
|
+
name=name,
|
|
926
|
+
signature=signature,
|
|
927
|
+
axioms=tuple(parser.axioms),
|
|
928
|
+
conjectures=tuple(parser.conjectures),
|
|
929
|
+
)
|