charm-crypto-framework 0.61.1__cp313-cp313-macosx_10_13_universal2.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.
- charm/__init__.py +5 -0
- charm/adapters/__init__.py +0 -0
- charm/adapters/abenc_adapt_hybrid.py +90 -0
- charm/adapters/dabenc_adapt_hybrid.py +145 -0
- charm/adapters/ibenc_adapt_hybrid.py +72 -0
- charm/adapters/ibenc_adapt_identityhash.py +80 -0
- charm/adapters/kpabenc_adapt_hybrid.py +91 -0
- charm/adapters/pkenc_adapt_bchk05.py +121 -0
- charm/adapters/pkenc_adapt_chk04.py +91 -0
- charm/adapters/pkenc_adapt_hybrid.py +98 -0
- charm/adapters/pksig_adapt_naor01.py +89 -0
- charm/config.py +7 -0
- charm/core/__init__.py +0 -0
- charm/core/benchmark/benchmark_util.c +353 -0
- charm/core/benchmark/benchmark_util.h +61 -0
- charm/core/benchmark/benchmarkmodule.c +476 -0
- charm/core/benchmark/benchmarkmodule.h +162 -0
- charm/core/benchmark.cpython-313-darwin.so +0 -0
- charm/core/crypto/AES/AES.c +1464 -0
- charm/core/crypto/AES.cpython-313-darwin.so +0 -0
- charm/core/crypto/DES/DES.c +113 -0
- charm/core/crypto/DES.cpython-313-darwin.so +0 -0
- charm/core/crypto/DES3/DES3.c +26 -0
- charm/core/crypto/DES3.cpython-313-darwin.so +0 -0
- charm/core/crypto/__init__.py +0 -0
- charm/core/crypto/cryptobase/XOR.c +80 -0
- charm/core/crypto/cryptobase/_counter.c +496 -0
- charm/core/crypto/cryptobase/_counter.h +54 -0
- charm/core/crypto/cryptobase/block_template.c +900 -0
- charm/core/crypto/cryptobase/block_template.h +69 -0
- charm/core/crypto/cryptobase/cryptobasemodule.c +220 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt.h +90 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_argchk.h +44 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_cfg.h +186 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_cipher.h +941 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_custom.h +556 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_des.c +1912 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_hash.h +407 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_mac.h +496 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_macros.h +435 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_math.h +534 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_misc.h +103 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_pk.h +653 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_pkcs.h +90 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_prng.h +199 -0
- charm/core/crypto/cryptobase/stream_template.c +271 -0
- charm/core/crypto/cryptobase/strxor.c +229 -0
- charm/core/crypto/cryptobase.cpython-313-darwin.so +0 -0
- charm/core/engine/__init__.py +5 -0
- charm/core/engine/protocol.py +293 -0
- charm/core/engine/util.py +174 -0
- charm/core/math/__init__.py +0 -0
- charm/core/math/elliptic_curve/ecmodule.c +1986 -0
- charm/core/math/elliptic_curve/ecmodule.h +230 -0
- charm/core/math/elliptic_curve.cpython-313-darwin.so +0 -0
- charm/core/math/elliptic_curve.pyi +63 -0
- charm/core/math/integer/integermodule.c +2539 -0
- charm/core/math/integer/integermodule.h +145 -0
- charm/core/math/integer.cpython-313-darwin.so +0 -0
- charm/core/math/integer.pyi +76 -0
- charm/core/math/pairing/miracl/miracl_config.h +37 -0
- charm/core/math/pairing/miracl/miracl_interface.h +118 -0
- charm/core/math/pairing/miracl/miracl_interface2.h +126 -0
- charm/core/math/pairing/miracl/pairingmodule2.c +2094 -0
- charm/core/math/pairing/miracl/pairingmodule2.h +307 -0
- charm/core/math/pairing/pairingmodule.c +2230 -0
- charm/core/math/pairing/pairingmodule.h +241 -0
- charm/core/math/pairing/relic/pairingmodule3.c +1853 -0
- charm/core/math/pairing/relic/pairingmodule3.h +233 -0
- charm/core/math/pairing/relic/relic_interface.c +1337 -0
- charm/core/math/pairing/relic/relic_interface.h +217 -0
- charm/core/math/pairing/relic/test_relic.c +171 -0
- charm/core/math/pairing.cpython-313-darwin.so +0 -0
- charm/core/math/pairing.pyi +69 -0
- charm/core/utilities/base64.c +248 -0
- charm/core/utilities/base64.h +15 -0
- charm/schemes/__init__.py +0 -0
- charm/schemes/abenc/__init__.py +0 -0
- charm/schemes/abenc/abenc_accountability_jyjxgd20.py +647 -0
- charm/schemes/abenc/abenc_bsw07.py +146 -0
- charm/schemes/abenc/abenc_ca_cpabe_ar17.py +684 -0
- charm/schemes/abenc/abenc_dacmacs_yj14.py +298 -0
- charm/schemes/abenc/abenc_lsw08.py +159 -0
- charm/schemes/abenc/abenc_maabe_rw15.py +236 -0
- charm/schemes/abenc/abenc_maabe_yj14.py +297 -0
- charm/schemes/abenc/abenc_tbpre_lww14.py +309 -0
- charm/schemes/abenc/abenc_unmcpabe_yahk14.py +223 -0
- charm/schemes/abenc/abenc_waters09.py +144 -0
- charm/schemes/abenc/abenc_yct14.py +208 -0
- charm/schemes/abenc/abenc_yllc15.py +178 -0
- charm/schemes/abenc/ac17.py +248 -0
- charm/schemes/abenc/bsw07.py +141 -0
- charm/schemes/abenc/cgw15.py +277 -0
- charm/schemes/abenc/dabe_aw11.py +204 -0
- charm/schemes/abenc/dfa_fe12.py +144 -0
- charm/schemes/abenc/pk_hve08.py +179 -0
- charm/schemes/abenc/waters11.py +143 -0
- charm/schemes/aggrsign_MuSig.py +150 -0
- charm/schemes/aggrsign_bls.py +267 -0
- charm/schemes/blindsig_ps16.py +654 -0
- charm/schemes/chamhash_adm05.py +113 -0
- charm/schemes/chamhash_rsa_hw09.py +100 -0
- charm/schemes/commit/__init__.py +0 -0
- charm/schemes/commit/commit_gs08.py +77 -0
- charm/schemes/commit/commit_pedersen92.py +53 -0
- charm/schemes/encap_bchk05.py +62 -0
- charm/schemes/grpsig/__init__.py +0 -0
- charm/schemes/grpsig/groupsig_bgls04.py +114 -0
- charm/schemes/grpsig/groupsig_bgls04_var.py +115 -0
- charm/schemes/hibenc/__init__.py +0 -0
- charm/schemes/hibenc/hibenc_bb04.py +105 -0
- charm/schemes/hibenc/hibenc_lew11.py +193 -0
- charm/schemes/ibenc/__init__.py +0 -0
- charm/schemes/ibenc/clpkc_rp03.py +119 -0
- charm/schemes/ibenc/ibenc_CW13_z.py +168 -0
- charm/schemes/ibenc/ibenc_bb03.py +94 -0
- charm/schemes/ibenc/ibenc_bf01.py +121 -0
- charm/schemes/ibenc/ibenc_ckrs09.py +120 -0
- charm/schemes/ibenc/ibenc_cllww12_z.py +172 -0
- charm/schemes/ibenc/ibenc_lsw08.py +120 -0
- charm/schemes/ibenc/ibenc_sw05.py +238 -0
- charm/schemes/ibenc/ibenc_waters05.py +144 -0
- charm/schemes/ibenc/ibenc_waters05_z.py +164 -0
- charm/schemes/ibenc/ibenc_waters09.py +107 -0
- charm/schemes/ibenc/ibenc_waters09_z.py +147 -0
- charm/schemes/joye_scheme.py +106 -0
- charm/schemes/lem_scheme.py +207 -0
- charm/schemes/pk_fre_ccv11.py +107 -0
- charm/schemes/pk_vrf.py +127 -0
- charm/schemes/pkenc/__init__.py +0 -0
- charm/schemes/pkenc/pkenc_cs98.py +108 -0
- charm/schemes/pkenc/pkenc_elgamal85.py +122 -0
- charm/schemes/pkenc/pkenc_gm82.py +98 -0
- charm/schemes/pkenc/pkenc_paillier99.py +118 -0
- charm/schemes/pkenc/pkenc_rabin.py +254 -0
- charm/schemes/pkenc/pkenc_rsa.py +186 -0
- charm/schemes/pksig/__init__.py +0 -0
- charm/schemes/pksig/pksig_CW13_z.py +135 -0
- charm/schemes/pksig/pksig_bls04.py +87 -0
- charm/schemes/pksig/pksig_boyen.py +156 -0
- charm/schemes/pksig/pksig_chch.py +97 -0
- charm/schemes/pksig/pksig_chp.py +70 -0
- charm/schemes/pksig/pksig_cl03.py +150 -0
- charm/schemes/pksig/pksig_cl04.py +87 -0
- charm/schemes/pksig/pksig_cllww12_z.py +142 -0
- charm/schemes/pksig/pksig_cyh.py +132 -0
- charm/schemes/pksig/pksig_dsa.py +76 -0
- charm/schemes/pksig/pksig_ecdsa.py +71 -0
- charm/schemes/pksig/pksig_hess.py +104 -0
- charm/schemes/pksig/pksig_hw.py +110 -0
- charm/schemes/pksig/pksig_lamport.py +63 -0
- charm/schemes/pksig/pksig_ps01.py +135 -0
- charm/schemes/pksig/pksig_ps02.py +124 -0
- charm/schemes/pksig/pksig_ps03.py +119 -0
- charm/schemes/pksig/pksig_rsa_hw09.py +206 -0
- charm/schemes/pksig/pksig_schnorr91.py +77 -0
- charm/schemes/pksig/pksig_waters.py +115 -0
- charm/schemes/pksig/pksig_waters05.py +121 -0
- charm/schemes/pksig/pksig_waters09.py +121 -0
- charm/schemes/pre_mg07.py +150 -0
- charm/schemes/prenc/pre_afgh06.py +126 -0
- charm/schemes/prenc/pre_bbs98.py +123 -0
- charm/schemes/prenc/pre_nal16.py +216 -0
- charm/schemes/protocol_a01.py +272 -0
- charm/schemes/protocol_ao00.py +215 -0
- charm/schemes/protocol_cns07.py +274 -0
- charm/schemes/protocol_schnorr91.py +125 -0
- charm/schemes/sigma1.py +64 -0
- charm/schemes/sigma2.py +129 -0
- charm/schemes/sigma3.py +126 -0
- charm/schemes/threshold/__init__.py +59 -0
- charm/schemes/threshold/dkls23_dkg.py +556 -0
- charm/schemes/threshold/dkls23_presign.py +1089 -0
- charm/schemes/threshold/dkls23_sign.py +761 -0
- charm/schemes/threshold/xrpl_wallet.py +967 -0
- charm/test/__init__.py +0 -0
- charm/test/adapters/__init__.py +0 -0
- charm/test/adapters/abenc_adapt_hybrid_test.py +29 -0
- charm/test/adapters/dabenc_adapt_hybrid_test.py +56 -0
- charm/test/adapters/ibenc_adapt_hybrid_test.py +36 -0
- charm/test/adapters/ibenc_adapt_identityhash_test.py +32 -0
- charm/test/adapters/kpabenc_adapt_hybrid_test.py +30 -0
- charm/test/benchmark/abenc_yllc15_bench.py +92 -0
- charm/test/benchmark/benchmark_test.py +148 -0
- charm/test/benchmark_threshold.py +260 -0
- charm/test/conftest.py +38 -0
- charm/test/fuzz/__init__.py +1 -0
- charm/test/fuzz/conftest.py +5 -0
- charm/test/fuzz/fuzz_policy_parser.py +76 -0
- charm/test/fuzz/fuzz_serialization.py +83 -0
- charm/test/schemes/__init__.py +0 -0
- charm/test/schemes/abenc/__init__.py +0 -0
- charm/test/schemes/abenc/abenc_bsw07_test.py +39 -0
- charm/test/schemes/abenc/abenc_dacmacs_yj14_test.py +16 -0
- charm/test/schemes/abenc/abenc_lsw08_test.py +33 -0
- charm/test/schemes/abenc/abenc_maabe_yj14_test.py +16 -0
- charm/test/schemes/abenc/abenc_tbpre_lww14_test.py +16 -0
- charm/test/schemes/abenc/abenc_waters09_test.py +38 -0
- charm/test/schemes/abenc/abenc_yllc15_test.py +74 -0
- charm/test/schemes/chamhash_adm05_test.py +31 -0
- charm/test/schemes/chamhash_rsa_hw09_test.py +29 -0
- charm/test/schemes/commit/__init__.py +0 -0
- charm/test/schemes/commit/commit_gs08_test.py +24 -0
- charm/test/schemes/commit/commit_pedersen92_test.py +26 -0
- charm/test/schemes/dabe_aw11_test.py +45 -0
- charm/test/schemes/encap_bchk05_test.py +21 -0
- charm/test/schemes/grpsig/__init__.py +0 -0
- charm/test/schemes/grpsig/groupsig_bgls04_test.py +35 -0
- charm/test/schemes/grpsig/groupsig_bgls04_var_test.py +39 -0
- charm/test/schemes/hibenc/__init__.py +0 -0
- charm/test/schemes/hibenc/hibenc_bb04_test.py +28 -0
- charm/test/schemes/ibenc/__init__.py +0 -0
- charm/test/schemes/ibenc/ibenc_bb03_test.py +26 -0
- charm/test/schemes/ibenc/ibenc_bf01_test.py +24 -0
- charm/test/schemes/ibenc/ibenc_ckrs09_test.py +25 -0
- charm/test/schemes/ibenc/ibenc_lsw08_test.py +31 -0
- charm/test/schemes/ibenc/ibenc_sw05_test.py +32 -0
- charm/test/schemes/ibenc/ibenc_waters05_test.py +31 -0
- charm/test/schemes/ibenc/ibenc_waters09_test.py +27 -0
- charm/test/schemes/pk_vrf_test.py +29 -0
- charm/test/schemes/pkenc/__init__.py +0 -0
- charm/test/schemes/pkenc_test.py +255 -0
- charm/test/schemes/pksig/__init__.py +0 -0
- charm/test/schemes/pksig_test.py +376 -0
- charm/test/schemes/rsa_alg_test.py +340 -0
- charm/test/schemes/threshold_test.py +1792 -0
- charm/test/serialize/__init__.py +0 -0
- charm/test/serialize/serialize_test.py +40 -0
- charm/test/toolbox/__init__.py +0 -0
- charm/test/toolbox/conversion_test.py +30 -0
- charm/test/toolbox/ecgroup_test.py +53 -0
- charm/test/toolbox/integer_arithmetic_test.py +441 -0
- charm/test/toolbox/paddingschemes_test.py +238 -0
- charm/test/toolbox/policy_parser_stress_test.py +969 -0
- charm/test/toolbox/secretshare_test.py +28 -0
- charm/test/toolbox/symcrypto_test.py +108 -0
- charm/test/toolbox/test_policy_expression.py +16 -0
- charm/test/vectors/__init__.py +1 -0
- charm/test/vectors/test_bls_vectors.py +289 -0
- charm/test/vectors/test_pedersen_vectors.py +315 -0
- charm/test/vectors/test_schnorr_vectors.py +368 -0
- charm/test/zkp_compiler/__init__.py +9 -0
- charm/test/zkp_compiler/benchmark_zkp.py +258 -0
- charm/test/zkp_compiler/test_and_proof.py +240 -0
- charm/test/zkp_compiler/test_batch_verify.py +248 -0
- charm/test/zkp_compiler/test_dleq_proof.py +264 -0
- charm/test/zkp_compiler/test_or_proof.py +231 -0
- charm/test/zkp_compiler/test_proof_serialization.py +121 -0
- charm/test/zkp_compiler/test_range_proof.py +241 -0
- charm/test/zkp_compiler/test_representation_proof.py +325 -0
- charm/test/zkp_compiler/test_schnorr_proof.py +221 -0
- charm/test/zkp_compiler/test_thread_safety.py +169 -0
- charm/test/zkp_compiler/test_zkp_parser.py +139 -0
- charm/toolbox/ABEnc.py +26 -0
- charm/toolbox/ABEncMultiAuth.py +66 -0
- charm/toolbox/ABEnumeric.py +800 -0
- charm/toolbox/Commit.py +24 -0
- charm/toolbox/DFA.py +89 -0
- charm/toolbox/FSA.py +1254 -0
- charm/toolbox/Hash.py +39 -0
- charm/toolbox/IBEnc.py +62 -0
- charm/toolbox/IBSig.py +64 -0
- charm/toolbox/PKEnc.py +66 -0
- charm/toolbox/PKSig.py +56 -0
- charm/toolbox/PREnc.py +32 -0
- charm/toolbox/ZKProof.py +289 -0
- charm/toolbox/__init__.py +0 -0
- charm/toolbox/bitstring.py +49 -0
- charm/toolbox/broadcast.py +220 -0
- charm/toolbox/conversion.py +100 -0
- charm/toolbox/eccurve.py +149 -0
- charm/toolbox/ecgroup.py +143 -0
- charm/toolbox/enum.py +60 -0
- charm/toolbox/hash_module.py +91 -0
- charm/toolbox/integergroup.py +323 -0
- charm/toolbox/iterate.py +22 -0
- charm/toolbox/matrixops.py +76 -0
- charm/toolbox/mpc_utils.py +296 -0
- charm/toolbox/msp.py +175 -0
- charm/toolbox/mta.py +985 -0
- charm/toolbox/node.py +120 -0
- charm/toolbox/ot/__init__.py +22 -0
- charm/toolbox/ot/base_ot.py +374 -0
- charm/toolbox/ot/dpf.py +642 -0
- charm/toolbox/ot/mpfss.py +228 -0
- charm/toolbox/ot/ot_extension.py +589 -0
- charm/toolbox/ot/silent_ot.py +378 -0
- charm/toolbox/paddingschemes.py +423 -0
- charm/toolbox/paddingschemes_test.py +238 -0
- charm/toolbox/pairingcurves.py +85 -0
- charm/toolbox/pairinggroup.py +186 -0
- charm/toolbox/policy_expression_spec.py +70 -0
- charm/toolbox/policytree.py +189 -0
- charm/toolbox/reCompiler.py +346 -0
- charm/toolbox/redundancyschemes.py +65 -0
- charm/toolbox/schemebase.py +188 -0
- charm/toolbox/secretshare.py +104 -0
- charm/toolbox/secretutil.py +174 -0
- charm/toolbox/securerandom.py +73 -0
- charm/toolbox/sigmaprotocol.py +46 -0
- charm/toolbox/specialprimes.py +45 -0
- charm/toolbox/symcrypto.py +279 -0
- charm/toolbox/threshold_sharing.py +553 -0
- charm/toolbox/xmlserialize.py +94 -0
- charm/toolbox/zknode.py +105 -0
- charm/zkp_compiler/__init__.py +89 -0
- charm/zkp_compiler/and_proof.py +460 -0
- charm/zkp_compiler/batch_verify.py +324 -0
- charm/zkp_compiler/dleq_proof.py +423 -0
- charm/zkp_compiler/or_proof.py +305 -0
- charm/zkp_compiler/range_proof.py +417 -0
- charm/zkp_compiler/representation_proof.py +466 -0
- charm/zkp_compiler/schnorr_proof.py +273 -0
- charm/zkp_compiler/thread_safe.py +150 -0
- charm/zkp_compiler/zk_demo.py +489 -0
- charm/zkp_compiler/zkp_factory.py +330 -0
- charm/zkp_compiler/zkp_generator.py +370 -0
- charm/zkp_compiler/zkparser.py +269 -0
- charm_crypto_framework-0.61.1.dist-info/METADATA +337 -0
- charm_crypto_framework-0.61.1.dist-info/RECORD +323 -0
- charm_crypto_framework-0.61.1.dist-info/WHEEL +5 -0
- charm_crypto_framework-0.61.1.dist-info/licenses/LICENSE.txt +165 -0
- charm_crypto_framework-0.61.1.dist-info/top_level.txt +1 -0
charm/toolbox/FSA.py
ADDED
|
@@ -0,0 +1,1254 @@
|
|
|
1
|
+
|
|
2
|
+
# Module FSA -- methods to manipulate finite-state automata
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
This module defines an FSA class, for representing and operating on
|
|
6
|
+
finite-state automata (FSAs). FSAs can be used to represent regular
|
|
7
|
+
expressions and to test sequences for membership in the languages
|
|
8
|
+
described by regular expressions.
|
|
9
|
+
|
|
10
|
+
FSAs can be deterministic or nondeterministic, and they can contain
|
|
11
|
+
epsilon transitions. Methods to determinize an automaton (also
|
|
12
|
+
eliminating its epsilon transitions), and to minimize an automaton,
|
|
13
|
+
are provided.
|
|
14
|
+
|
|
15
|
+
The transition labels for an FSA can be symbols from an alphabet, as
|
|
16
|
+
in the standard formal definition of an FSA, but they can also be
|
|
17
|
+
instances which represent predicates. If these instances implement
|
|
18
|
+
instance.matches(), then the FSA nextState() function and accepts()
|
|
19
|
+
predicate can be used. If they implement instance.complement() and
|
|
20
|
+
instance.intersection(), the FSA can be be determinized and minimized,
|
|
21
|
+
to find a minimal deterministic FSA that accepts an equivalent
|
|
22
|
+
language.
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
Quick Start
|
|
26
|
+
----------
|
|
27
|
+
Instances of FSA can be created out of labels (for instance, strings)
|
|
28
|
+
by the singleton() function, and combined to create more complex FSAs
|
|
29
|
+
through the complement(), closure(), concatenation(), union(), and
|
|
30
|
+
other constructors. For example, concatenation(singleton('a'),
|
|
31
|
+
union(singleton('b'), closure(singleton('c')))) creates an FSA that
|
|
32
|
+
accepts the strings 'a', 'ab', 'ac', 'acc', 'accc', and so on.
|
|
33
|
+
|
|
34
|
+
Instances of FSA can also be created with the compileRE() function,
|
|
35
|
+
which compiles a simple regular expression (using only '*', '?', '+',
|
|
36
|
+
'|', '(', and ')' as metacharacters) into an FSA. For example,
|
|
37
|
+
compileRE('a(b|c*)') returns an FSA equivalent to the example in the
|
|
38
|
+
previous paragraph.
|
|
39
|
+
|
|
40
|
+
FSAs can be determinized, to create equivalent FSAs (FSAs accepting
|
|
41
|
+
the same language) with unique successor states for each input, and
|
|
42
|
+
minimized, to create an equivalent deterministic FSA with the smallest
|
|
43
|
+
number of states. FSAs can also be complemented, intersected, unioned,
|
|
44
|
+
and so forth as described under 'FSA Functions' below.
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
FSA Methods
|
|
48
|
+
-----------
|
|
49
|
+
The class FSA defines the following methods.
|
|
50
|
+
|
|
51
|
+
Acceptance
|
|
52
|
+
``````````
|
|
53
|
+
fsa.nextStates(state, input)
|
|
54
|
+
returns a list of states
|
|
55
|
+
fsa.nextState(state, input)
|
|
56
|
+
returns None or a single state if
|
|
57
|
+
|nextStates| <= 1, otherwise it raises an exception
|
|
58
|
+
fsa.nextStateSet(states, input)
|
|
59
|
+
returns a list of states
|
|
60
|
+
fsa.accepts(sequence)
|
|
61
|
+
returns true or false
|
|
62
|
+
|
|
63
|
+
Accessors and predicates
|
|
64
|
+
````````````````````````
|
|
65
|
+
isEmpty()
|
|
66
|
+
returns true iff the language accepted by the FSA is the empty language
|
|
67
|
+
labels()
|
|
68
|
+
returns a list of labels that are used in any transition
|
|
69
|
+
nextAvailableState()
|
|
70
|
+
returns an integer n such that no states in the FSA
|
|
71
|
+
are numeric values >= n
|
|
72
|
+
|
|
73
|
+
Reductions
|
|
74
|
+
``````````
|
|
75
|
+
sorted(initial=0)
|
|
76
|
+
returns an equivalent FSA whose states are numbered
|
|
77
|
+
upwards from 0
|
|
78
|
+
determinized()
|
|
79
|
+
returns an equivalent deterministic FSA
|
|
80
|
+
minimized()
|
|
81
|
+
returns an equivalent minimal FSA
|
|
82
|
+
trimmed()
|
|
83
|
+
returns an equivalent FSA that contains no unreachable or dead
|
|
84
|
+
states
|
|
85
|
+
|
|
86
|
+
Presentation
|
|
87
|
+
````````````
|
|
88
|
+
toDotString()
|
|
89
|
+
returns a string suitable as *.dot file for the 'dot'
|
|
90
|
+
program from AT&T GraphViz
|
|
91
|
+
view()
|
|
92
|
+
views the FSA with a gs viewer, if gs and dot are installed
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
FSA Functions
|
|
96
|
+
------------
|
|
97
|
+
Construction from FSAs
|
|
98
|
+
``````````````````````
|
|
99
|
+
complement(a)
|
|
100
|
+
returns an fsa that accepts exactly those sequences that its
|
|
101
|
+
argument does not
|
|
102
|
+
closure(a)
|
|
103
|
+
returns an fsa that accepts sequences composed of zero or more
|
|
104
|
+
concatenations of sequences accepted by the argument
|
|
105
|
+
concatenation(a, b)
|
|
106
|
+
returns an fsa that accepts sequences composed of a
|
|
107
|
+
sequence accepted by a, followed by a sequence accepted by b
|
|
108
|
+
containment(a, occurrences=1)
|
|
109
|
+
returns an fsa that accepts sequences that
|
|
110
|
+
contain at least occurrences occurrences of a subsequence recognized by the
|
|
111
|
+
argument.
|
|
112
|
+
difference(a, b)
|
|
113
|
+
returns an fsa that accepts those sequences accepted by a
|
|
114
|
+
but not b
|
|
115
|
+
intersection(a, b)
|
|
116
|
+
returns an fsa that accepts sequences accepted by both a
|
|
117
|
+
and b
|
|
118
|
+
iteration(a, min=1, max=None)
|
|
119
|
+
returns an fsa that accepts sequences
|
|
120
|
+
consisting of from min to max (or any number, if max is None) of sequences
|
|
121
|
+
accepted by its first argument
|
|
122
|
+
option(a)
|
|
123
|
+
equivalent to union(a, EMPTY_STRING_FSA)
|
|
124
|
+
reverse(a)
|
|
125
|
+
returns an fsa that accepts strings whose reversal is accepted by
|
|
126
|
+
the argument
|
|
127
|
+
union(a, b)
|
|
128
|
+
returns an fsa that accepts sequences accepted by both a and b
|
|
129
|
+
|
|
130
|
+
Predicates
|
|
131
|
+
``````````
|
|
132
|
+
equivalent(a, b)
|
|
133
|
+
returns true iff a and b accept the same language
|
|
134
|
+
|
|
135
|
+
Reductions (these equivalent to the similarly-named methods)
|
|
136
|
+
````````````````````````````````````````````````````````````
|
|
137
|
+
determinize(fsa)
|
|
138
|
+
returns an equivalent deterministic FSA
|
|
139
|
+
minimize(fsa)
|
|
140
|
+
returns an equivalent minimal FSA
|
|
141
|
+
sort(fsa, initial=0)
|
|
142
|
+
returns an equivalent FSA whose states are numbered from
|
|
143
|
+
initial
|
|
144
|
+
trim(fsa)
|
|
145
|
+
returns an equivalent FSA that contains no dead or unreachable
|
|
146
|
+
states
|
|
147
|
+
|
|
148
|
+
Construction from labels
|
|
149
|
+
````````````````````````
|
|
150
|
+
compileRE(string)
|
|
151
|
+
returns an FSA that accepts the language described by
|
|
152
|
+
string, where string is a list of symbols and '*', '+', '?', and '|' operators,
|
|
153
|
+
with '(' and ')' to control precedence.
|
|
154
|
+
sequence(sequence)
|
|
155
|
+
returns an fsa that accepts sequences that are matched by
|
|
156
|
+
the elements of the argument. For example, sequence('abc') returns an fsa that
|
|
157
|
+
accepts 'abc' and ['a', 'b', 'c'].
|
|
158
|
+
singleton(label)
|
|
159
|
+
returns an fsa that accepts singletons whose elements are
|
|
160
|
+
matched by label. For example, singleton('a') returns an fsa that accepts only
|
|
161
|
+
the string 'a'.
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
FSA Constants
|
|
165
|
+
------------
|
|
166
|
+
EMPTY_STRING_FSA is an FSA that accepts the language consisting only
|
|
167
|
+
of the empty string.
|
|
168
|
+
|
|
169
|
+
NULL_FSA is an FSA that accepts the null language.
|
|
170
|
+
|
|
171
|
+
UNIVERSAL_FSA is an FSA that accepts S*, where S is any object.
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
FSA instance creation
|
|
175
|
+
---------------------
|
|
176
|
+
FSA is initialized with a list of states, an alphabet, a list of
|
|
177
|
+
transition, an initial state, and a list of final states. If fsa is an
|
|
178
|
+
FSA, fsa.tuple() returns these values in that order, i.e. (states,
|
|
179
|
+
alphabet, transitions, initialState, finalStates). They're also
|
|
180
|
+
available as fields of fsa with those names.
|
|
181
|
+
|
|
182
|
+
Each element of transition is a tuple of a start state, an end state,
|
|
183
|
+
and a label: (startState, endSTate, label).
|
|
184
|
+
|
|
185
|
+
If the list of states is None, it's computed from initialState,
|
|
186
|
+
finalStates, and the states in transitions.
|
|
187
|
+
|
|
188
|
+
If alphabet is None, an open alphabet is used: labels are assumed to
|
|
189
|
+
be objects that implements label.matches(input), label.complement(),
|
|
190
|
+
and label.intersection() as follows:
|
|
191
|
+
|
|
192
|
+
- label.matches(input) returns true iff label matches input
|
|
193
|
+
- label.complement() returnseither a label or a list of labels which,
|
|
194
|
+
together with the receiver, partition the input alphabet
|
|
195
|
+
- label.intersection(other) returns either None (if label and other don't
|
|
196
|
+
both match any symbol), or a label that matches the set of symbols that
|
|
197
|
+
both label and other match
|
|
198
|
+
|
|
199
|
+
As a special case, strings can be used as labels. If a strings 'a' and
|
|
200
|
+
'b' are used as a label and there's no alphabet, '~a' and '~b' are
|
|
201
|
+
their respective complements, and '~a&~b' is the intersection of '~a'
|
|
202
|
+
and '~b'. (The intersections of 'a' and 'b', 'a' and '~b', and '~a'
|
|
203
|
+
and 'b' are, respectively, None, 'a', and 'b'.)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
Goals
|
|
207
|
+
-----
|
|
208
|
+
Design Goals:
|
|
209
|
+
|
|
210
|
+
- easy to use
|
|
211
|
+
- easy to read (simple implementation, direct expression of algorithms)
|
|
212
|
+
- extensible
|
|
213
|
+
|
|
214
|
+
Non-Goals:
|
|
215
|
+
|
|
216
|
+
- efficiency
|
|
217
|
+
"""
|
|
218
|
+
|
|
219
|
+
__author__ = "Oliver Steele <steele@osteele.com>"
|
|
220
|
+
|
|
221
|
+
# Python 3 port of the FSA module
|
|
222
|
+
from functools import reduce
|
|
223
|
+
|
|
224
|
+
#try:
|
|
225
|
+
# import NumFSAUtils
|
|
226
|
+
#except ImportError:
|
|
227
|
+
NumFSAUtils = None
|
|
228
|
+
|
|
229
|
+
ANY = 'ANY'
|
|
230
|
+
EPSILON = None
|
|
231
|
+
|
|
232
|
+
TRACE_LABEL_MULTIPLICATIONS = 0
|
|
233
|
+
NUMPY_DETERMINIZATION_CUTOFF = 50
|
|
234
|
+
|
|
235
|
+
class FSA:
|
|
236
|
+
def __init__(self, states, alphabet, transitions, initialState, finalStates, arcMetadata=[]):
|
|
237
|
+
if states == None:
|
|
238
|
+
states = self.collectStates(transitions, initialState, finalStates)
|
|
239
|
+
else:
|
|
240
|
+
#assert not filter(lambda s, states=states:s not in states, self.collectStates(transitions, initialState, finalStates))
|
|
241
|
+
assert list(filter(lambda s, states=states:s not in states, self.collectStates(transitions, initialState, finalStates))) != None
|
|
242
|
+
self.states = states
|
|
243
|
+
self.alphabet = alphabet
|
|
244
|
+
self.transitions = transitions
|
|
245
|
+
self.initialState = initialState
|
|
246
|
+
self.finalStates = finalStates
|
|
247
|
+
self.setArcMetadata(arcMetadata)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
#
|
|
251
|
+
# Initialization
|
|
252
|
+
#
|
|
253
|
+
def makeStateTable(self, default=None):
|
|
254
|
+
for state in self.states:
|
|
255
|
+
if type(state) != int:
|
|
256
|
+
return {}
|
|
257
|
+
if reduce(min, self.states) < 0: return {}
|
|
258
|
+
if reduce(max, self.states) > max(100, len(self.states) * 2): return {}
|
|
259
|
+
return [default] * (reduce(max, self.states) + 1)
|
|
260
|
+
|
|
261
|
+
def initializeTransitionTables(self):
|
|
262
|
+
self._transitionsFrom = self.makeStateTable()
|
|
263
|
+
for s in self.states:
|
|
264
|
+
self._transitionsFrom[s] = []
|
|
265
|
+
for transition in self.transitions:
|
|
266
|
+
s, _, label = transition
|
|
267
|
+
self._transitionsFrom[s].append(transition)
|
|
268
|
+
|
|
269
|
+
def collectStates(self, transitions, initialState, finalStates):
|
|
270
|
+
states = finalStates[:]
|
|
271
|
+
if initialState not in states:
|
|
272
|
+
states.append(initialState)
|
|
273
|
+
for s0, s1, _ in transitions:
|
|
274
|
+
if s0 not in states: states.append(s0)
|
|
275
|
+
if s1 not in states: states.append(s1)
|
|
276
|
+
states.sort()
|
|
277
|
+
return states
|
|
278
|
+
|
|
279
|
+
def computeEpsilonClosure(self, state):
|
|
280
|
+
states = [state]
|
|
281
|
+
index = 0
|
|
282
|
+
while index < len(states):
|
|
283
|
+
state, index = states[index], index + 1
|
|
284
|
+
for _, s, label in self.transitionsFrom(state):
|
|
285
|
+
if label == EPSILON and s not in states:
|
|
286
|
+
states.append(s)
|
|
287
|
+
states.sort()
|
|
288
|
+
return states
|
|
289
|
+
|
|
290
|
+
def computeEpsilonClosures(self):
|
|
291
|
+
self._epsilonClosures = self.makeStateTable()
|
|
292
|
+
for s in self.states:
|
|
293
|
+
self._epsilonClosures[s] = self.computeEpsilonClosure(s)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
#
|
|
297
|
+
# Copying
|
|
298
|
+
#
|
|
299
|
+
def create(self, *args):
|
|
300
|
+
return self.__class__(*args)
|
|
301
|
+
|
|
302
|
+
def copy(self, *args):
|
|
303
|
+
copy = self.__class__(*args)
|
|
304
|
+
if hasattr(self, 'label'):
|
|
305
|
+
copy.label = self.label
|
|
306
|
+
if hasattr(self, 'source'):
|
|
307
|
+
copy.source = self.source
|
|
308
|
+
return copy
|
|
309
|
+
|
|
310
|
+
def creationArgs(self):
|
|
311
|
+
return self.tuple() + (self.getArcMetadata(),)
|
|
312
|
+
|
|
313
|
+
def coerce(self, klass):
|
|
314
|
+
copy = klass(*self.creationArgs())
|
|
315
|
+
if hasattr(self, 'source'):
|
|
316
|
+
copy.source = self.source
|
|
317
|
+
return copy
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
#
|
|
321
|
+
# Accessors
|
|
322
|
+
#
|
|
323
|
+
def epsilonClosure(self, state):
|
|
324
|
+
try:
|
|
325
|
+
return self._epsilonClosures[state]
|
|
326
|
+
except AttributeError:
|
|
327
|
+
self.computeEpsilonClosures()
|
|
328
|
+
return self._epsilonClosures[state]
|
|
329
|
+
|
|
330
|
+
def labels(self):
|
|
331
|
+
"""Returns a list of transition labels."""
|
|
332
|
+
labels = []
|
|
333
|
+
for (_, _, label) in self.transitions:
|
|
334
|
+
if label and label not in labels:
|
|
335
|
+
labels.append(label)
|
|
336
|
+
return labels
|
|
337
|
+
|
|
338
|
+
def nextAvailableState(self):
|
|
339
|
+
return reduce(max, [s for s in self.states if type(s) == int], -1) + 1
|
|
340
|
+
|
|
341
|
+
def transitionsFrom(self, state):
|
|
342
|
+
try:
|
|
343
|
+
return self._transitionsFrom[state]
|
|
344
|
+
except AttributeError:
|
|
345
|
+
self.initializeTransitionTables()
|
|
346
|
+
return self._transitionsFrom[state]
|
|
347
|
+
|
|
348
|
+
def tuple(self):
|
|
349
|
+
return self.states, self.alphabet, self.transitions, self.initialState, self.finalStates
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
#
|
|
353
|
+
# Arc Metadata Accessors
|
|
354
|
+
#
|
|
355
|
+
def hasArcMetadata(self):
|
|
356
|
+
return hasattr(self, '_arcMetadata')
|
|
357
|
+
|
|
358
|
+
def getArcMetadata(self):
|
|
359
|
+
return list(getattr(self, '_arcMetadata', {}).items())
|
|
360
|
+
|
|
361
|
+
def setArcMetadata(self, list):
|
|
362
|
+
arcMetadata = {}
|
|
363
|
+
for (arc, data) in list:
|
|
364
|
+
arcMetadata[arc] = data
|
|
365
|
+
self._arcMetadata = arcMetadata
|
|
366
|
+
|
|
367
|
+
def addArcMetadata(self, list):
|
|
368
|
+
for (arc, data) in list:
|
|
369
|
+
self.addArcMetadataFor(arc, data)
|
|
370
|
+
|
|
371
|
+
def addArcMetadataFor(self, transition, data):
|
|
372
|
+
if not hasattr(self, '_arcMetadata'):
|
|
373
|
+
self._arcMetadata = {}
|
|
374
|
+
oldData = self._arcMetadata.get(transition)
|
|
375
|
+
if oldData:
|
|
376
|
+
for item in data:
|
|
377
|
+
if item not in oldData:
|
|
378
|
+
oldData.append(item)
|
|
379
|
+
else:
|
|
380
|
+
self._arcMetadata[transition] = data
|
|
381
|
+
|
|
382
|
+
def setArcMetadataFor(self, transition, data):
|
|
383
|
+
if not hasattr(self, '_arcMetadata'):
|
|
384
|
+
self._arcMetadata = {}
|
|
385
|
+
self._arcMetadata[transition] = data
|
|
386
|
+
|
|
387
|
+
def getArcMetadataFor(self, transition, default=None):
|
|
388
|
+
return getattr(self, '_arcMetadata', {}).get(transition, default)
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
#
|
|
392
|
+
# Predicates
|
|
393
|
+
#
|
|
394
|
+
def isEmpty(self):
|
|
395
|
+
return not self.minimized().finalStates
|
|
396
|
+
|
|
397
|
+
def isFSA(self):
|
|
398
|
+
return 1
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
#
|
|
402
|
+
# Accepting
|
|
403
|
+
#
|
|
404
|
+
def labelMatches(self, label, input):
|
|
405
|
+
return labelMatches(label, input)
|
|
406
|
+
|
|
407
|
+
def nextStates(self, state, input):
|
|
408
|
+
states = []
|
|
409
|
+
for _, sink, label in self.transitionsFrom(state):
|
|
410
|
+
if self.labelMatches(label, input) and sink not in states:
|
|
411
|
+
states.extend(self.epsilonClosure(sink))
|
|
412
|
+
return states
|
|
413
|
+
|
|
414
|
+
def nextState(self, state, input):
|
|
415
|
+
states = self.nextStates(state, input)
|
|
416
|
+
assert len(states) <= 1
|
|
417
|
+
return states and states[0]
|
|
418
|
+
|
|
419
|
+
def nextStateSet(self, states, input):
|
|
420
|
+
successors = []
|
|
421
|
+
for state in states:
|
|
422
|
+
for _, sink, label in self.transitionsFrom(state):
|
|
423
|
+
if self.labelMatches(label, input) and sink not in successors:
|
|
424
|
+
successors.append(sink)
|
|
425
|
+
return successors
|
|
426
|
+
|
|
427
|
+
def accepts(self, sequence):
|
|
428
|
+
states = [self.initialState]
|
|
429
|
+
for item in sequence:
|
|
430
|
+
newStates = []
|
|
431
|
+
for state in states:
|
|
432
|
+
for s1 in self.nextStates(state, item):
|
|
433
|
+
if s1 not in newStates:
|
|
434
|
+
newStates.append(s1)
|
|
435
|
+
states = newStates
|
|
436
|
+
return len(list(filter(lambda s, finals=self.finalStates:s in finals, states))) > 0
|
|
437
|
+
|
|
438
|
+
def getTransitions(self, sequence):
|
|
439
|
+
states = [self.initialState]
|
|
440
|
+
transitions = {}
|
|
441
|
+
count = 1
|
|
442
|
+
for item in sequence:
|
|
443
|
+
newStates = []
|
|
444
|
+
for state in states:
|
|
445
|
+
for s1 in self.nextStates(state, item):
|
|
446
|
+
if s1 not in newStates:
|
|
447
|
+
transitions[ count ] = (int(state), int(s1), str(item))
|
|
448
|
+
count += 1
|
|
449
|
+
# print("s1: " % (state, s1, str(item)))
|
|
450
|
+
newStates.append(s1)
|
|
451
|
+
states = newStates
|
|
452
|
+
if len(list(filter(lambda s, finals=self.finalStates:s in finals, states))) > 0:
|
|
453
|
+
return transitions
|
|
454
|
+
return False
|
|
455
|
+
|
|
456
|
+
#
|
|
457
|
+
# FSA operations
|
|
458
|
+
#
|
|
459
|
+
def complement(self):
|
|
460
|
+
states, alpha, transitions, start, finals = completion(self.determinized()).tuple()
|
|
461
|
+
return self.create(states, alpha, transitions, start, list(filter(lambda s,f=finals:s not in f, states)))#.trimmed()
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
#
|
|
465
|
+
# Reductions
|
|
466
|
+
#
|
|
467
|
+
def sorted(self, initial=0):
|
|
468
|
+
if hasattr(self, '_isSorted'):
|
|
469
|
+
return self
|
|
470
|
+
stateMap = {}
|
|
471
|
+
nextState = initial
|
|
472
|
+
states, index = [self.initialState], 0
|
|
473
|
+
while index < len(states) or len(states) < len(self.states):
|
|
474
|
+
if index >= len(states):
|
|
475
|
+
for state in self.states:
|
|
476
|
+
if stateMap.get(state) == None:
|
|
477
|
+
break
|
|
478
|
+
states.append(state)
|
|
479
|
+
state, index = states[index], index + 1
|
|
480
|
+
new, nextState = nextState, nextState + 1
|
|
481
|
+
stateMap[state] = new
|
|
482
|
+
for _, s, _ in self.transitionsFrom(state):
|
|
483
|
+
if s not in states:
|
|
484
|
+
states.append(s)
|
|
485
|
+
states = list(stateMap.values())
|
|
486
|
+
transitions = list(map(lambda s, m=stateMap:(m[s[0]], m[s[1]], s[2]), self.transitions))
|
|
487
|
+
arcMetadata = list(map(lambda s, data, m=stateMap:((m[s[0]], m[s[1]], s[2]), data), self.getArcMetadata()))
|
|
488
|
+
copy = self.copy(states, self.alphabet, transitions, stateMap[self.initialState], list(map(stateMap.get, self.finalStates)), arcMetadata)
|
|
489
|
+
copy._isSorted = 1
|
|
490
|
+
return copy
|
|
491
|
+
|
|
492
|
+
def trimmed(self):
|
|
493
|
+
"""Returns an equivalent FSA that doesn't include unreachable states,
|
|
494
|
+
or states that only lead to dead states."""
|
|
495
|
+
if hasattr(self, '_isTrimmed'):
|
|
496
|
+
return self
|
|
497
|
+
states, alpha, transitions, initial, finals = self.tuple()
|
|
498
|
+
reachable, index = [initial], 0
|
|
499
|
+
while index < len(reachable):
|
|
500
|
+
state, index = reachable[index], index + 1
|
|
501
|
+
for (_, s, _) in self.transitionsFrom(state):
|
|
502
|
+
if s not in reachable:
|
|
503
|
+
reachable.append(s)
|
|
504
|
+
endable, index = list(finals), 0
|
|
505
|
+
while index < len(endable):
|
|
506
|
+
state, index = endable[index], index + 1
|
|
507
|
+
for (s0, s1, _) in transitions:
|
|
508
|
+
if s1 == state and s0 not in endable:
|
|
509
|
+
endable.append(s0)
|
|
510
|
+
states = []
|
|
511
|
+
for s in reachable:
|
|
512
|
+
if s in endable:
|
|
513
|
+
states.append(s)
|
|
514
|
+
if not states:
|
|
515
|
+
if self.__class__ == FSA:
|
|
516
|
+
return NULL_FSA
|
|
517
|
+
else:
|
|
518
|
+
return NULL_FSA.coerce(self.__class__)
|
|
519
|
+
transitions = list(filter(lambda s, states=states: s[0] in states and s[1] in states, transitions))
|
|
520
|
+
arcMetadata = list(filter(lambda s, states=states: s[0] in states and s[1] in states, self.getArcMetadata()))
|
|
521
|
+
result = self.copy(states, alpha, transitions, initial, list(filter(lambda s, states=states:s in states, finals)), arcMetadata).sorted()
|
|
522
|
+
result._isTrimmed = 1
|
|
523
|
+
return result
|
|
524
|
+
|
|
525
|
+
def withoutEpsilons(self):
|
|
526
|
+
# replace each state by its epsilon closure
|
|
527
|
+
states0, alphabet, transitions0, initial0, finals0 = self.tuple()
|
|
528
|
+
initial = self.epsilonClosure(self.initialState)
|
|
529
|
+
initial.sort()
|
|
530
|
+
initial = tuple(initial)
|
|
531
|
+
stateSets, index = [initial], 0
|
|
532
|
+
transitions = []
|
|
533
|
+
while index < len(stateSets):
|
|
534
|
+
stateSet, index = stateSets[index], index + 1
|
|
535
|
+
for (s0, s1, label) in transitions0:
|
|
536
|
+
if s0 in stateSet and label:
|
|
537
|
+
target = self.epsilonClosure(s1)
|
|
538
|
+
target.sort()
|
|
539
|
+
target = tuple(target)
|
|
540
|
+
transition = (stateSet, target, label)
|
|
541
|
+
if transition not in transitions:
|
|
542
|
+
transitions.append(transition)
|
|
543
|
+
if target not in stateSets:
|
|
544
|
+
stateSets.append(target)
|
|
545
|
+
finalStates = []
|
|
546
|
+
for stateSet in stateSets:
|
|
547
|
+
if list(filter(lambda s, finalStates=self.finalStates:s in finalStates, stateSet)):
|
|
548
|
+
finalStates.append(stateSet)
|
|
549
|
+
copy = self.copy(stateSets, alphabet, transitions, stateSets[0], finalStates).sorted()
|
|
550
|
+
copy._isTrimmed = 1
|
|
551
|
+
return copy
|
|
552
|
+
|
|
553
|
+
def determinized(self):
|
|
554
|
+
"""Returns a deterministic FSA that accepts the same language."""
|
|
555
|
+
if hasattr(self, '_isDeterminized'):
|
|
556
|
+
return self
|
|
557
|
+
if len(self.states) > NUMPY_DETERMINIZATION_CUTOFF and NumFSAUtils and not self.getArcMetadata():
|
|
558
|
+
data = NumFSAUtils.determinize(*self.tuple() + (self.epsilonClosure,))
|
|
559
|
+
result = apply(self.copy, data).sorted()
|
|
560
|
+
result._isDeterminized = 1
|
|
561
|
+
return result
|
|
562
|
+
transitions = []
|
|
563
|
+
stateSets, index = [tuple(self.epsilonClosure(self.initialState))], 0
|
|
564
|
+
arcMetadata = []
|
|
565
|
+
while index < len(stateSets):
|
|
566
|
+
stateSet, index = stateSets[index], index + 1
|
|
567
|
+
localTransitions = list(filter(lambda s, set=stateSet:s[2] and s[0] in set, self.transitions))
|
|
568
|
+
if localTransitions:
|
|
569
|
+
localLabels = list(map(lambda s:s[2], localTransitions))
|
|
570
|
+
labelMap = constructLabelMap(localLabels, self.alphabet)
|
|
571
|
+
labelTargets = {} # a map from labels to target states
|
|
572
|
+
for transition in localTransitions:
|
|
573
|
+
_, s1, l1 = transition
|
|
574
|
+
for label, positives in labelMap:
|
|
575
|
+
if l1 in positives:
|
|
576
|
+
successorStates = labelTargets[label] = labelTargets.get(label) or []
|
|
577
|
+
for s2 in self.epsilonClosure(s1):
|
|
578
|
+
if s2 not in successorStates:
|
|
579
|
+
successorStates.append(s2)
|
|
580
|
+
if self.getArcMetadataFor(transition):
|
|
581
|
+
arcMetadata.append(((stateSet, successorStates, label), self.getArcMetadataFor(transition)))
|
|
582
|
+
for label, successorStates in list(labelTargets.items()):
|
|
583
|
+
successorStates.sort()
|
|
584
|
+
successorStates = tuple(successorStates)
|
|
585
|
+
transitions.append((stateSet, successorStates, label))
|
|
586
|
+
if successorStates not in stateSets:
|
|
587
|
+
stateSets.append(successorStates)
|
|
588
|
+
finalStates = []
|
|
589
|
+
for stateSet in stateSets:
|
|
590
|
+
if list(filter(lambda s,finalStates=self.finalStates:s in finalStates, stateSet)):
|
|
591
|
+
finalStates.append(stateSet)
|
|
592
|
+
if arcMetadata:
|
|
593
|
+
def fixArc(pair):
|
|
594
|
+
(s0, s1, label), data = pair
|
|
595
|
+
s1.sort()
|
|
596
|
+
s1 = tuple(s1)
|
|
597
|
+
return ((s0, s1, label), data)
|
|
598
|
+
arcMetadata = list(map(fixArc, arcMetadata))
|
|
599
|
+
result = self.copy(stateSets, self.alphabet, transitions, stateSets[0], finalStates, arcMetadata).sorted()
|
|
600
|
+
result._isDeterminized = 1
|
|
601
|
+
result._isTrimmed = 1
|
|
602
|
+
return result
|
|
603
|
+
|
|
604
|
+
def minimized(self):
|
|
605
|
+
"""Returns a minimal FSA that accepts the same language."""
|
|
606
|
+
if hasattr(self, '_isMinimized'):
|
|
607
|
+
return self
|
|
608
|
+
self = self.trimmed().determinized()
|
|
609
|
+
states0, alpha0, transitions0, initial0, finals0 = self.tuple()
|
|
610
|
+
sinkState = self.nextAvailableState()
|
|
611
|
+
labels = self.labels()
|
|
612
|
+
states = [_f for _f in [
|
|
613
|
+
tuple(filter(lambda s, finalStates=self.finalStates:s not in finalStates, states0)),
|
|
614
|
+
tuple(filter(lambda s, finalStates=self.finalStates:s in finalStates, states0))] if _f]
|
|
615
|
+
labelMap = {}
|
|
616
|
+
for state in states0:
|
|
617
|
+
for label in labels:
|
|
618
|
+
found = 0
|
|
619
|
+
for s0, s1, l in self.transitionsFrom(state):
|
|
620
|
+
if l == label:
|
|
621
|
+
assert not found
|
|
622
|
+
found = 1
|
|
623
|
+
labelMap[(state, label)] = s1
|
|
624
|
+
changed = 1
|
|
625
|
+
iteration = 0
|
|
626
|
+
while changed:
|
|
627
|
+
changed = 0
|
|
628
|
+
iteration = iteration + 1
|
|
629
|
+
#print 'iteration', iteration
|
|
630
|
+
partitionMap = {sinkState: sinkState}
|
|
631
|
+
for set in states:
|
|
632
|
+
for state in set:
|
|
633
|
+
partitionMap[state] = set
|
|
634
|
+
#print 'states =', states
|
|
635
|
+
for index in range(len(states)):
|
|
636
|
+
set = states[index]
|
|
637
|
+
if len(set) > 1:
|
|
638
|
+
for label in labels:
|
|
639
|
+
destinationMap = {}
|
|
640
|
+
for state in set:
|
|
641
|
+
nextSet = partitionMap[labelMap.get((state, label), sinkState)]
|
|
642
|
+
targets = destinationMap[nextSet] = destinationMap.get(nextSet) or []
|
|
643
|
+
targets.append(state)
|
|
644
|
+
#print 'destinationMap from', set, label, ' =', destinationMap
|
|
645
|
+
if len(list(destinationMap.values())) > 1:
|
|
646
|
+
values = list(destinationMap.values())
|
|
647
|
+
#print 'splitting', destinationMap.keys()
|
|
648
|
+
for value in values:
|
|
649
|
+
value.sort()
|
|
650
|
+
states[index:index+1] = list(map(tuple, values))
|
|
651
|
+
changed = 1
|
|
652
|
+
break
|
|
653
|
+
transitions = removeDuplicates(list(map(lambda s, m=partitionMap:(m[s[0]], m[s[1]], s[2]), transitions0)))
|
|
654
|
+
arcMetadata = list(map(lambda s, data, m=partitionMap:((m[s[0]], m[s[1]], s[2]), data), self.getArcMetadata()))
|
|
655
|
+
if not alpha0:
|
|
656
|
+
newTransitions = consolidateTransitions(transitions)
|
|
657
|
+
if arcMetadata:
|
|
658
|
+
newArcMetadata = []
|
|
659
|
+
for transition, data in arcMetadata:
|
|
660
|
+
s0, s1, label = transition
|
|
661
|
+
for newTransition in newTransitions:
|
|
662
|
+
if newTransition[0] == s0 and newTransition[1] == s1 and labelIntersection(newTransition[2], label):
|
|
663
|
+
newArcMetadata.append((newTransition, data))
|
|
664
|
+
arcMetadata = newArcMetadata
|
|
665
|
+
transitions = newTransitions
|
|
666
|
+
initial = partitionMap[initial0]
|
|
667
|
+
finals = removeDuplicates(list(map(lambda s, m=partitionMap:m[s], finals0)))
|
|
668
|
+
result = self.copy(states, self.alphabet, transitions, initial, finals, arcMetadata).sorted()
|
|
669
|
+
result._isDeterminized = 1
|
|
670
|
+
result._isMinimized = 1
|
|
671
|
+
result._isTrimmed = 1
|
|
672
|
+
return result
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
#
|
|
676
|
+
# Presentation Methods
|
|
677
|
+
#
|
|
678
|
+
def __repr__(self):
|
|
679
|
+
if hasattr(self, 'label') and self.label:
|
|
680
|
+
return '<%s on %s>' % (self.__class__.__name__, self.label)
|
|
681
|
+
else:
|
|
682
|
+
return '<%s.%s instance>' % (self.__class__.__module__, self.__class__.__name__)
|
|
683
|
+
|
|
684
|
+
def __str__(self):
|
|
685
|
+
import string
|
|
686
|
+
output = []
|
|
687
|
+
output.append('%s {' % (self.__class__.__name__,))
|
|
688
|
+
output.append('\tinitialState = ' + str(self.initialState) + ';')
|
|
689
|
+
if self.finalStates:
|
|
690
|
+
output.append('\tfinalStates = ' + string.join(list(map(str, self.finalStates)), ', ') + ';')
|
|
691
|
+
transitions = list(self.transitions)
|
|
692
|
+
transitions.sort()
|
|
693
|
+
for transition in transitions:
|
|
694
|
+
(s0, s1, label) = transition
|
|
695
|
+
additionalInfo = self.additionalTransitionInfoString(transition)
|
|
696
|
+
output.append('\t%s -> %s %s%s;' % (s0, s1, labelString(label), additionalInfo and ' ' + additionalInfo or ''));
|
|
697
|
+
output.append('}');
|
|
698
|
+
return string.join(output, '\n')
|
|
699
|
+
|
|
700
|
+
def additionalTransitionInfoString(self, transition):
|
|
701
|
+
if self.getArcMetadataFor(transition):
|
|
702
|
+
import string
|
|
703
|
+
return '<' + string.join(list(map(str, self.getArcMetadataFor(transition))), ', ') + '>'
|
|
704
|
+
|
|
705
|
+
def stateLabelString(self, state):
|
|
706
|
+
"""A template method for specifying a state's label, for use in dot
|
|
707
|
+
diagrams. If this returns None, the default (the string representation
|
|
708
|
+
of the state) is used."""
|
|
709
|
+
return None
|
|
710
|
+
|
|
711
|
+
def toDotString(self):
|
|
712
|
+
"""Returns a string that can be printed by the DOT tool at
|
|
713
|
+
http://www.research.att.com/sw/tools/graphviz/ ."""
|
|
714
|
+
import string
|
|
715
|
+
output = []
|
|
716
|
+
output.append('digraph finite_state_machine {');
|
|
717
|
+
if self.finalStates:
|
|
718
|
+
output.append('\tnode [shape = doublecircle]; ' + string.join(list(map(str, self.finalStates)), '; ') + ';' );
|
|
719
|
+
output.append('\tnode [shape = circle];');
|
|
720
|
+
output.append('\trankdir=LR;');
|
|
721
|
+
output.append('\t%s [style = bold];' % (self.initialState,))
|
|
722
|
+
for state in self.states:
|
|
723
|
+
if self.stateLabelString(state):
|
|
724
|
+
output.append('\t%s [label = "%s"];' % (state, string.replace(self.stateLabelString(state), '\n', '\\n')))
|
|
725
|
+
transitions = list(self.transitions)
|
|
726
|
+
transitions.sort()
|
|
727
|
+
for (s0, s1, label) in transitions:
|
|
728
|
+
output.append('\t%s -> %s [label = "%s"];' % (s0, s1, string.replace(labelString(label), '\n', '\\n')));
|
|
729
|
+
output.append('}');
|
|
730
|
+
return string.join(output, '\n')
|
|
731
|
+
|
|
732
|
+
def view(self):
|
|
733
|
+
view(self.toDotString())
|
|
734
|
+
|
|
735
|
+
|
|
736
|
+
#
|
|
737
|
+
# Recognizers for special-case languages
|
|
738
|
+
#
|
|
739
|
+
|
|
740
|
+
NULL_FSA = FSA([0], None, [], 0, [])
|
|
741
|
+
EMPTY_STRING_FSA = FSA([0], None, [], 0, [0])
|
|
742
|
+
UNIVERSAL_FSA = FSA([0], None, [(0, 0, ANY)], 0, [0])
|
|
743
|
+
|
|
744
|
+
#
|
|
745
|
+
# Utility functions
|
|
746
|
+
#
|
|
747
|
+
|
|
748
|
+
def removeDuplicates(sequence):
|
|
749
|
+
result = []
|
|
750
|
+
for x in sequence:
|
|
751
|
+
if x not in result:
|
|
752
|
+
result.append(x)
|
|
753
|
+
return result
|
|
754
|
+
|
|
755
|
+
def toFSA(arg):
|
|
756
|
+
if hasattr(arg, 'isFSA') and arg.isFSA:
|
|
757
|
+
return arg
|
|
758
|
+
else:
|
|
759
|
+
return singleton(arg)
|
|
760
|
+
|
|
761
|
+
def view(str):
|
|
762
|
+
import os, tempfile, subprocess
|
|
763
|
+
# Use NamedTemporaryFile for secure temp file creation (avoids race conditions)
|
|
764
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.dot', delete=False) as dotf:
|
|
765
|
+
dotfile = dotf.name
|
|
766
|
+
dotf.write(str)
|
|
767
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.ps', delete=False) as psf:
|
|
768
|
+
psfile = psf.name
|
|
769
|
+
dotter = 'dot'
|
|
770
|
+
psviewer = 'gv'
|
|
771
|
+
psoptions = '-antialias'
|
|
772
|
+
try:
|
|
773
|
+
subprocess.run([dotter, '-Tps', dotfile, '-o', psfile], check=False)
|
|
774
|
+
subprocess.run([psviewer, psoptions, psfile], check=False)
|
|
775
|
+
finally:
|
|
776
|
+
# Clean up temp files
|
|
777
|
+
if os.path.exists(dotfile):
|
|
778
|
+
os.unlink(dotfile)
|
|
779
|
+
if os.path.exists(psfile):
|
|
780
|
+
os.unlink(psfile)
|
|
781
|
+
|
|
782
|
+
|
|
783
|
+
#
|
|
784
|
+
# Operations on languages (via their recognizers)
|
|
785
|
+
# These generally return nondeterministic FSAs.
|
|
786
|
+
#
|
|
787
|
+
|
|
788
|
+
def closure(arg):
|
|
789
|
+
fsa = toFSA(arg)
|
|
790
|
+
states, alpha, transitions, initial, finals = fsa.tuple()
|
|
791
|
+
final = fsa.nextAvailableState()
|
|
792
|
+
transitions = transitions[:]
|
|
793
|
+
for s in finals:
|
|
794
|
+
transitions.append((s, final, None))
|
|
795
|
+
transitions.append((initial, final, None))
|
|
796
|
+
transitions.append((final, initial, None))
|
|
797
|
+
return fsa.create(states + [final], alpha, transitions, initial, [final], fsa.getArcMetadata())
|
|
798
|
+
|
|
799
|
+
def complement(arg):
|
|
800
|
+
"""Returns an FSA that accepts exactly those strings that the argument does
|
|
801
|
+
not."""
|
|
802
|
+
return toFSA(arg).complement()
|
|
803
|
+
|
|
804
|
+
def concatenation(a, *args):
|
|
805
|
+
"""Returns an FSA that accepts the language consisting of the concatenation
|
|
806
|
+
of strings recognized by the arguments."""
|
|
807
|
+
a = toFSA(a)
|
|
808
|
+
for b in args:
|
|
809
|
+
b = toFSA(b).sorted(a.nextAvailableState())
|
|
810
|
+
states0, alpha0, transitions0, initial0, finals0 = a.tuple()
|
|
811
|
+
states1, alpha1, transitions1, initial1, finals1 = b.tuple()
|
|
812
|
+
a = a.create(states0 + states1, alpha0, transitions0 + transitions1 + list(map(lambda s0, s1=initial1:(s0, s1, EPSILON), finals0)), initial0, finals1, a.getArcMetadata() + b.getArcMetadata())
|
|
813
|
+
return a
|
|
814
|
+
|
|
815
|
+
def containment(arg, occurrences=1):
|
|
816
|
+
"""Returns an FSA that matches sequences containing at least _count_
|
|
817
|
+
occurrences
|
|
818
|
+
of _symbol_."""
|
|
819
|
+
arg = toFSA(arg)
|
|
820
|
+
fsa = closure(singleton(ANY))
|
|
821
|
+
for i in range(occurrences):
|
|
822
|
+
fsa = concatenation(fsa, concatenation(arg, closure(singleton(ANY))))
|
|
823
|
+
return fsa
|
|
824
|
+
|
|
825
|
+
def difference(a, b):
|
|
826
|
+
"""Returns an FSA that accepts those strings accepted by the first
|
|
827
|
+
argument, but not the second."""
|
|
828
|
+
return intersection(a, complement(b))
|
|
829
|
+
|
|
830
|
+
def equivalent(a, b):
|
|
831
|
+
"""Return true ifff a and b accept the same language."""
|
|
832
|
+
return difference(a, b).isEmpty() and difference(b, a).isEmpty()
|
|
833
|
+
|
|
834
|
+
def intersection(a, b):
|
|
835
|
+
"""Returns the intersection of two FSAs"""
|
|
836
|
+
a, b = completion(a.determinized()), completion(b.determinized())
|
|
837
|
+
states0, alpha0, transitions0, start0, finals0 = a.tuple()
|
|
838
|
+
states1, alpha1, transitions1, start1, finals1 = b.tuple()
|
|
839
|
+
states = [(start0, start1)]
|
|
840
|
+
index = 0
|
|
841
|
+
transitions = []
|
|
842
|
+
arcMetadata = []
|
|
843
|
+
buildArcMetadata = a.hasArcMetadata() or b.hasArcMetadata()
|
|
844
|
+
while index < len(states):
|
|
845
|
+
state, index = states[index], index + 1
|
|
846
|
+
for sa0, sa1, la in a.transitionsFrom(state[0]):
|
|
847
|
+
for sb0, sb1, lb in b.transitionsFrom(state[1]):
|
|
848
|
+
label = labelIntersection(la, lb)
|
|
849
|
+
if label:
|
|
850
|
+
s = (sa1, sb1)
|
|
851
|
+
transition = (state, s, label)
|
|
852
|
+
transitions.append(transition)
|
|
853
|
+
if s not in states:
|
|
854
|
+
states.append(s)
|
|
855
|
+
if buildArcMetadata:
|
|
856
|
+
if a.getArcMetadataFor((sa0, sa1, la)):
|
|
857
|
+
arcMetadata.append((transition, a.getArcMetadataFor((sa0, sa1, la))))
|
|
858
|
+
if b.getArcMetadataFor((sa0, sa1, la)):
|
|
859
|
+
arcMetadata.append((transition, b.getArcMetadataFor((sa0, sa1, la))))
|
|
860
|
+
finals = list(filter(lambda s0, s1, f0=finals0, f1=finals1:s0 in f0 and s1 in f1, states))
|
|
861
|
+
return a.create(states, alpha0, transitions, states[0], finals, arcMetadata).sorted()
|
|
862
|
+
|
|
863
|
+
def iteration(fsa, min=1, max=None):
|
|
864
|
+
"""
|
|
865
|
+
### equivalent(iteration(singleton('a', 0, 2)), compileRE('|a|aa'))
|
|
866
|
+
### equivalent(iteration(singleton('a', 1, 2)), compileRE('a|aa'))
|
|
867
|
+
### equivalent(iteration(singleton('a', 1)), compileRE('aa*'))
|
|
868
|
+
"""
|
|
869
|
+
if min:
|
|
870
|
+
return concatenation(fsa, iteration(fsa, min=min - 1, max=(max and max - 1)))
|
|
871
|
+
elif max:
|
|
872
|
+
return option(concatenation(fsa), iteration(fsa, min=min, max=max - 1))
|
|
873
|
+
else:
|
|
874
|
+
return closure(fsa)
|
|
875
|
+
|
|
876
|
+
def option(fsa):
|
|
877
|
+
return union(fsa, EMPTY_STRING_FSA)
|
|
878
|
+
|
|
879
|
+
def reverse(fsa):
|
|
880
|
+
states, alpha, transitions, initial, finals = fsa.tuple()
|
|
881
|
+
newInitial = fsa.nextAvailableState()
|
|
882
|
+
return fsa.create(states + [newInitial], alpha, list(map(lambda s0, s1, l:(s1, s0, l), transitions)) + list(map(lambda s1, s0=newInitial:(s0, s1, EPSILON), finals)), [initial])
|
|
883
|
+
|
|
884
|
+
def union(*args):
|
|
885
|
+
initial, final = 1, 2
|
|
886
|
+
states, transitions = [initial, final], []
|
|
887
|
+
arcMetadata = []
|
|
888
|
+
for arg in args:
|
|
889
|
+
arg = toFSA(arg).sorted(reduce(max, states) + 1)
|
|
890
|
+
states1, alpha1, transitions1, initial1, finals1 = arg.tuple()
|
|
891
|
+
states.extend(states1)
|
|
892
|
+
transitions.extend(list(transitions1))
|
|
893
|
+
transitions.append((initial, initial1, None))
|
|
894
|
+
for s in finals1:
|
|
895
|
+
transitions.append((s, final, None))
|
|
896
|
+
arcMetadata.extend(arg.getArcMetadata())
|
|
897
|
+
if len(args):
|
|
898
|
+
return toFSA(args[0]).create(states, alpha1, transitions, initial, [final], arcMetadata)
|
|
899
|
+
else:
|
|
900
|
+
return FSA(states, alpha1, transitions, initial, [final])
|
|
901
|
+
|
|
902
|
+
|
|
903
|
+
#
|
|
904
|
+
# FSA Functions
|
|
905
|
+
#
|
|
906
|
+
|
|
907
|
+
def completion(fsa):
|
|
908
|
+
"""Returns an FSA that accepts the same language as the argument, but that
|
|
909
|
+
lands in a defined state for every input."""
|
|
910
|
+
states, alphabet, transitions, start, finals = fsa.tuple()
|
|
911
|
+
transitions = transitions[:]
|
|
912
|
+
sinkState = fsa.nextAvailableState()
|
|
913
|
+
for state in states:
|
|
914
|
+
labels = list(map(lambda _, __, label:label, fsa.transitionsFrom(state)))
|
|
915
|
+
for label in complementLabelSet(labels, alphabet):
|
|
916
|
+
transitions.append(state, sinkState, label)
|
|
917
|
+
if alphabet:
|
|
918
|
+
transitions.extend(list(map(lambda symbol, s=sinkState:(s, s, symbol), alphabet)))
|
|
919
|
+
else:
|
|
920
|
+
transitions.append((sinkState, sinkState, ANY))
|
|
921
|
+
return fsa.copy(states + [sinkState], alphabet, transitions, start, finals, fsa.getArcMetadata())
|
|
922
|
+
|
|
923
|
+
def determinize(fsa):
|
|
924
|
+
return fsa.determinized()
|
|
925
|
+
|
|
926
|
+
def minimize(fsa):
|
|
927
|
+
return fsa.minimized()
|
|
928
|
+
|
|
929
|
+
def sort(fsa):
|
|
930
|
+
return fsa.sorted()
|
|
931
|
+
|
|
932
|
+
def trim(fsa):
|
|
933
|
+
return fsa.trimmed()
|
|
934
|
+
|
|
935
|
+
|
|
936
|
+
#
|
|
937
|
+
# Label operations
|
|
938
|
+
#
|
|
939
|
+
|
|
940
|
+
TRACE_LABEL_OPERATIONS = 0
|
|
941
|
+
|
|
942
|
+
def labelComplements(label, alphabet):
|
|
943
|
+
complement = labelComplement(label, alphabet) or []
|
|
944
|
+
if TRACE_LABEL_OPERATIONS:
|
|
945
|
+
print('complement(%s) = %s' % (label, complement))
|
|
946
|
+
if type(complement) != list:
|
|
947
|
+
complement = [complement]
|
|
948
|
+
return complement
|
|
949
|
+
|
|
950
|
+
def labelComplement(label, alphabet):
|
|
951
|
+
if hasattr(label, 'complement'): # == InstanceType:
|
|
952
|
+
return label.complement()
|
|
953
|
+
elif alphabet:
|
|
954
|
+
return list(filter(lambda s, s1=label:s != s1, alphabet))
|
|
955
|
+
elif label == ANY:
|
|
956
|
+
return None
|
|
957
|
+
else:
|
|
958
|
+
return symbolComplement(label)
|
|
959
|
+
|
|
960
|
+
def labelIntersection(l1, l2):
|
|
961
|
+
intersection = _labelIntersection(l1, l2)
|
|
962
|
+
if TRACE_LABEL_OPERATIONS:
|
|
963
|
+
print('intersection(%s, %s) = %s' % (l1, l2, intersection))
|
|
964
|
+
return intersection
|
|
965
|
+
|
|
966
|
+
def _labelIntersection(l1, l2):
|
|
967
|
+
if l1 == l2:
|
|
968
|
+
return l1
|
|
969
|
+
#todo: is the following ever true
|
|
970
|
+
elif not l1 or not l2:
|
|
971
|
+
return None
|
|
972
|
+
elif l1 == ANY:
|
|
973
|
+
return l2
|
|
974
|
+
elif l2 == ANY:
|
|
975
|
+
return l1
|
|
976
|
+
elif hasattr(l1, 'intersection'):
|
|
977
|
+
return l1.intersection(l2)
|
|
978
|
+
elif hasattr(l2, 'intersection'):
|
|
979
|
+
return l2.intersection(l1)
|
|
980
|
+
else:
|
|
981
|
+
return symbolIntersection(l1, l2)
|
|
982
|
+
|
|
983
|
+
def labelString(label):
|
|
984
|
+
return str(label)
|
|
985
|
+
|
|
986
|
+
def labelMatches(label, input):
|
|
987
|
+
if hasattr(label, 'matches'):
|
|
988
|
+
return label.matches(input)
|
|
989
|
+
else:
|
|
990
|
+
return label == input
|
|
991
|
+
|
|
992
|
+
|
|
993
|
+
#
|
|
994
|
+
# Label set operations
|
|
995
|
+
#
|
|
996
|
+
|
|
997
|
+
TRACE_LABEL_SET_OPERATIONS = 0
|
|
998
|
+
|
|
999
|
+
def complementLabelSet(labels, alphabet=None):
|
|
1000
|
+
if not labels:
|
|
1001
|
+
return alphabet or [ANY]
|
|
1002
|
+
result = labelComplements(labels[0], alphabet)
|
|
1003
|
+
for label in labels[1:]:
|
|
1004
|
+
result = intersectLabelSets(labelComplements(label, alphabet), result)
|
|
1005
|
+
if TRACE_LABEL_SET_OPERATIONS:
|
|
1006
|
+
print('complement(%s) = %s' % (labels, result))
|
|
1007
|
+
return result
|
|
1008
|
+
|
|
1009
|
+
def intersectLabelSets(alist, blist):
|
|
1010
|
+
clist = []
|
|
1011
|
+
for a in alist:
|
|
1012
|
+
for b in blist:
|
|
1013
|
+
c = labelIntersection(a, b)
|
|
1014
|
+
if c:
|
|
1015
|
+
clist.append(c)
|
|
1016
|
+
if TRACE_LABEL_SET_OPERATIONS:
|
|
1017
|
+
print('intersection%s = %s' % ((alist, blist), clist))
|
|
1018
|
+
return clist
|
|
1019
|
+
|
|
1020
|
+
def unionLabelSets(alist, blist, alphabet=None):
|
|
1021
|
+
result = complementLabelSet(intersectLabelSets(complementLabelSet(alist, alphabet), complementLabelSet(blist, alphabet)), alphabet)
|
|
1022
|
+
if TRACE_LABEL_SET_OPERATIONS:
|
|
1023
|
+
print('union%s = %s' % ((alist, blist), result))
|
|
1024
|
+
return result
|
|
1025
|
+
|
|
1026
|
+
|
|
1027
|
+
#
|
|
1028
|
+
# Transition and Label utility operations
|
|
1029
|
+
#
|
|
1030
|
+
|
|
1031
|
+
TRACE_CONSOLIDATE_TRANSITIONS = 0
|
|
1032
|
+
TRACE_CONSTRUCT_LABEL_MAP = 0
|
|
1033
|
+
|
|
1034
|
+
def consolidateTransitions(transitions):
|
|
1035
|
+
result = []
|
|
1036
|
+
for s0, s1 in removeDuplicates(list(map(lambda s:(s[0],s[1]), transitions))):
|
|
1037
|
+
labels = []
|
|
1038
|
+
for ss0, ss1, label in transitions:
|
|
1039
|
+
if ss0 == s0 and ss1 == s1:
|
|
1040
|
+
labels.append(label)
|
|
1041
|
+
if len(labels) > 1:
|
|
1042
|
+
reduced = reduce(unionLabelSets, [[label] for label in labels])
|
|
1043
|
+
if TRACE_LABEL_OPERATIONS or TRACE_CONSOLIDATE_TRANSITIONS:
|
|
1044
|
+
print('consolidateTransitions(%s) -> %s' % (labels, reduced))
|
|
1045
|
+
labels = reduced
|
|
1046
|
+
for label in labels:
|
|
1047
|
+
result.append((s0, s1, label))
|
|
1048
|
+
return result
|
|
1049
|
+
|
|
1050
|
+
def constructLabelMap(labels, alphabet, includeComplements=0):
|
|
1051
|
+
"""Return a list of (newLabel, positives), where newLabel is an
|
|
1052
|
+
intersection of elements from labels and their complemens, and positives is
|
|
1053
|
+
a list of labels that have non-empty intersections with newLabel."""
|
|
1054
|
+
label = labels[0]
|
|
1055
|
+
#if hasattr(label, 'constructLabelMap'):
|
|
1056
|
+
# return label.constructLabelMap(labels)
|
|
1057
|
+
complements = labelComplements(label, alphabet)
|
|
1058
|
+
if len(labels) == 1:
|
|
1059
|
+
results = [(label, [label])]
|
|
1060
|
+
if includeComplements:
|
|
1061
|
+
for complement in complements:
|
|
1062
|
+
results.append((complement, []))
|
|
1063
|
+
return results
|
|
1064
|
+
results = []
|
|
1065
|
+
for newLabel, positives in constructLabelMap(labels[1:], alphabet, includeComplements=1):
|
|
1066
|
+
newPositive = labelIntersection(label, newLabel)
|
|
1067
|
+
if newPositive:
|
|
1068
|
+
results.append((newPositive, [label] + positives))
|
|
1069
|
+
for complement in complements:
|
|
1070
|
+
if positives or includeComplements:
|
|
1071
|
+
newNegative = labelIntersection(complement, newLabel)
|
|
1072
|
+
if newNegative:
|
|
1073
|
+
results.append((newNegative, positives))
|
|
1074
|
+
if TRACE_CONSTRUCT_LABEL_MAP:
|
|
1075
|
+
print('consolidateTransitions(%s) -> %s' % (labels, results))
|
|
1076
|
+
return results
|
|
1077
|
+
|
|
1078
|
+
|
|
1079
|
+
#
|
|
1080
|
+
# Symbol operations
|
|
1081
|
+
#
|
|
1082
|
+
|
|
1083
|
+
def symbolComplement(symbol):
|
|
1084
|
+
if '&' in symbol:
|
|
1085
|
+
import string
|
|
1086
|
+
return list(map(symbolComplement, string.split(symbol, '&')))
|
|
1087
|
+
elif symbol[0] == '~':
|
|
1088
|
+
return symbol[1:]
|
|
1089
|
+
else:
|
|
1090
|
+
return '~' + symbol
|
|
1091
|
+
|
|
1092
|
+
def symbolIntersection(s1, s2):
|
|
1093
|
+
import string
|
|
1094
|
+
set1 = string.split(s1, '&')
|
|
1095
|
+
set2 = string.split(s2, '&')
|
|
1096
|
+
for symbol in set1:
|
|
1097
|
+
if symbolComplement(symbol) in set2:
|
|
1098
|
+
return None
|
|
1099
|
+
for symbol in set2:
|
|
1100
|
+
if symbol not in set1:
|
|
1101
|
+
set1.append(symbol)
|
|
1102
|
+
nonNegatedSymbols = [s for s in set1 if s[0] != '~']
|
|
1103
|
+
if len(nonNegatedSymbols) > 1:
|
|
1104
|
+
return None
|
|
1105
|
+
if nonNegatedSymbols:
|
|
1106
|
+
return nonNegatedSymbols[0]
|
|
1107
|
+
set1.sort()
|
|
1108
|
+
return string.join(set1, '&')
|
|
1109
|
+
|
|
1110
|
+
|
|
1111
|
+
#
|
|
1112
|
+
# Construction from labels
|
|
1113
|
+
#
|
|
1114
|
+
|
|
1115
|
+
def singleton(symbol, alphabet=None, arcMetadata=None):
|
|
1116
|
+
fsa = FSA([0,1], alphabet, [(0, 1, symbol)], 0, [1])
|
|
1117
|
+
if arcMetadata:
|
|
1118
|
+
fsa.setArcMetadataFor((0, 1, symbol), arcMetadata)
|
|
1119
|
+
fsa.label = str(symbol)
|
|
1120
|
+
return fsa
|
|
1121
|
+
|
|
1122
|
+
def sequence(sequence, alphabet=None):
|
|
1123
|
+
fsa = reduce(concatenation, list(map(lambda label, alphabet=alphabet:singleton(label, alphabet), sequence)), EMPTY_STRING_FSA)
|
|
1124
|
+
fsa.label = str(sequence)
|
|
1125
|
+
return fsa
|
|
1126
|
+
|
|
1127
|
+
|
|
1128
|
+
#
|
|
1129
|
+
# Compiling Regular Expressions
|
|
1130
|
+
#
|
|
1131
|
+
|
|
1132
|
+
def compileRE(s, **options):
|
|
1133
|
+
import string
|
|
1134
|
+
if not options.get('multichar'):
|
|
1135
|
+
s = string.replace(s, ' ', '')
|
|
1136
|
+
fsa, index = compileREExpr(s + ')', 0, options)
|
|
1137
|
+
if index < len(s):
|
|
1138
|
+
raise 'extra ' + str(')')
|
|
1139
|
+
fsa.label = str(s)
|
|
1140
|
+
return fsa.minimized()
|
|
1141
|
+
|
|
1142
|
+
def compileREExpr(str, index, options):
|
|
1143
|
+
fsa = None
|
|
1144
|
+
while index < len(str) and str[index] != ')':
|
|
1145
|
+
fsa2, index = compileConjunction(str, index, options)
|
|
1146
|
+
if str[index] == '|': index = index + 1
|
|
1147
|
+
fsa = (fsa and union(fsa, fsa2)) or fsa2
|
|
1148
|
+
return (fsa or EMPTY_STRING_FSA), index
|
|
1149
|
+
|
|
1150
|
+
def compileConjunction(str, index, options):
|
|
1151
|
+
fsa = UNIVERSAL_FSA
|
|
1152
|
+
while str[index] not in ')|':
|
|
1153
|
+
conjunct, index = compileSequence(str, index, options)
|
|
1154
|
+
if str[index] == '&': index = index + 1
|
|
1155
|
+
fsa = intersection(fsa, conjunct)
|
|
1156
|
+
return fsa, index
|
|
1157
|
+
|
|
1158
|
+
def compileSequence(str, index, options):
|
|
1159
|
+
fsa = EMPTY_STRING_FSA
|
|
1160
|
+
while str[index] not in ')|&':
|
|
1161
|
+
fsa2, index = compileItem(str, index, options)
|
|
1162
|
+
fsa = concatenation(fsa, fsa2)
|
|
1163
|
+
return fsa, index
|
|
1164
|
+
|
|
1165
|
+
def compileItem(str, index, options):
|
|
1166
|
+
c , index = str[index], index + 1
|
|
1167
|
+
while c == ' ':
|
|
1168
|
+
c, index = str[index], index + 1
|
|
1169
|
+
if c == '(':
|
|
1170
|
+
fsa, index = compileREExpr(str, index, options)
|
|
1171
|
+
assert str[index] == ')'
|
|
1172
|
+
index = index + 1
|
|
1173
|
+
elif c == '.':
|
|
1174
|
+
fsa = singleton(ANY)
|
|
1175
|
+
elif c == '~':
|
|
1176
|
+
fsa, index = compileItem(str, index, options)
|
|
1177
|
+
fsa = complement(fsa)
|
|
1178
|
+
else:
|
|
1179
|
+
label = c
|
|
1180
|
+
if options.get('multichar'):
|
|
1181
|
+
import string
|
|
1182
|
+
while str[index] in string.letters or str[index] in string.digits:
|
|
1183
|
+
label, index = label + str[index], index + 1
|
|
1184
|
+
if str[index] == ':':
|
|
1185
|
+
index = index + 1
|
|
1186
|
+
upper = label
|
|
1187
|
+
lower, index = str[index], index + 1
|
|
1188
|
+
if upper == '0':
|
|
1189
|
+
upper = EPSILON
|
|
1190
|
+
if lower == '0':
|
|
1191
|
+
lower = EPSILON
|
|
1192
|
+
label = (upper, lower)
|
|
1193
|
+
fsa = singleton(label)
|
|
1194
|
+
while str[index] in '?*+':
|
|
1195
|
+
c, index = str[index], index + 1
|
|
1196
|
+
if c == '*':
|
|
1197
|
+
fsa = closure(fsa)
|
|
1198
|
+
elif c == '?':
|
|
1199
|
+
fsa = union(fsa, EMPTY_STRING_FSA)
|
|
1200
|
+
elif c == '+':
|
|
1201
|
+
fsa = iteration(fsa)
|
|
1202
|
+
else:
|
|
1203
|
+
raise ValueError('Unimplemented')
|
|
1204
|
+
return fsa, index
|
|
1205
|
+
|
|
1206
|
+
"""
|
|
1207
|
+
TRACE_LABEL_OPERATIONS = 1
|
|
1208
|
+
TRACE_LABEL_OPERATIONS = 0
|
|
1209
|
+
|
|
1210
|
+
print compileRE('')
|
|
1211
|
+
print compileRE('a')
|
|
1212
|
+
print compileRE('ab')
|
|
1213
|
+
print compileRE('abc')
|
|
1214
|
+
print compileRE('ab*')
|
|
1215
|
+
print compileRE('a*b')
|
|
1216
|
+
print compileRE('ab*c')
|
|
1217
|
+
print compileRE('ab?c')
|
|
1218
|
+
print compileRE('ab+c')
|
|
1219
|
+
print compileRE('ab|c')
|
|
1220
|
+
print compileRE('a(b|c)')
|
|
1221
|
+
|
|
1222
|
+
print compileRE('abc').accepts('abc')
|
|
1223
|
+
print compileRE('abc').accepts('ab')
|
|
1224
|
+
|
|
1225
|
+
print singleton('1', alphabet=['1']).minimized()
|
|
1226
|
+
print complement(singleton('1')).minimized()
|
|
1227
|
+
print singleton('1', alphabet=['1'])
|
|
1228
|
+
print completion(singleton('1'))
|
|
1229
|
+
print completion(singleton('1', alphabet=['1']))
|
|
1230
|
+
print complement(singleton('1', alphabet=['1']))
|
|
1231
|
+
print complement(singleton('1', alphabet=['1', '2']))
|
|
1232
|
+
print complement(singleton('1', alphabet=['1', '2'])).minimized()
|
|
1233
|
+
|
|
1234
|
+
print intersection(compileRE('a*b'), compileRE('ab*'))
|
|
1235
|
+
print intersection(compileRE('a*cb'), compileRE('acb*'))
|
|
1236
|
+
print difference(compileRE('ab*'), compileRE('abb')).minimized()
|
|
1237
|
+
|
|
1238
|
+
print compileRE('n.*v.*n')
|
|
1239
|
+
print compileRE('n.*v.*n&.*n.*n.*n.*')
|
|
1240
|
+
|
|
1241
|
+
print intersection(compileRE('n.*v.*n'), compileRE('.*n.*n.*n.*'))
|
|
1242
|
+
print difference(compileRE('n.*v.*n'), compileRE('.*n.*n.*n.*'))
|
|
1243
|
+
print difference(difference(compileRE('n.*v.*n'), compileRE('.*n.*n.*n.*')), compileRE('.*v.*v.*'))
|
|
1244
|
+
|
|
1245
|
+
print compileRE('a|~a').minimized()
|
|
1246
|
+
|
|
1247
|
+
|
|
1248
|
+
print containment(singleton('a'), 2).minimized()
|
|
1249
|
+
print difference(containment(singleton('a'), 2), containment(singleton('a'), 3)).minimized()
|
|
1250
|
+
print difference(containment(singleton('a'), 3), containment(singleton('a'), 2)).minimized()
|
|
1251
|
+
|
|
1252
|
+
print difference(compileRE('a*b'), compileRE('ab*')).minimized()
|
|
1253
|
+
|
|
1254
|
+
"""
|