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.
Files changed (323) hide show
  1. charm/__init__.py +5 -0
  2. charm/adapters/__init__.py +0 -0
  3. charm/adapters/abenc_adapt_hybrid.py +90 -0
  4. charm/adapters/dabenc_adapt_hybrid.py +145 -0
  5. charm/adapters/ibenc_adapt_hybrid.py +72 -0
  6. charm/adapters/ibenc_adapt_identityhash.py +80 -0
  7. charm/adapters/kpabenc_adapt_hybrid.py +91 -0
  8. charm/adapters/pkenc_adapt_bchk05.py +121 -0
  9. charm/adapters/pkenc_adapt_chk04.py +91 -0
  10. charm/adapters/pkenc_adapt_hybrid.py +98 -0
  11. charm/adapters/pksig_adapt_naor01.py +89 -0
  12. charm/config.py +7 -0
  13. charm/core/__init__.py +0 -0
  14. charm/core/benchmark/benchmark_util.c +353 -0
  15. charm/core/benchmark/benchmark_util.h +61 -0
  16. charm/core/benchmark/benchmarkmodule.c +476 -0
  17. charm/core/benchmark/benchmarkmodule.h +162 -0
  18. charm/core/benchmark.cpython-313-darwin.so +0 -0
  19. charm/core/crypto/AES/AES.c +1464 -0
  20. charm/core/crypto/AES.cpython-313-darwin.so +0 -0
  21. charm/core/crypto/DES/DES.c +113 -0
  22. charm/core/crypto/DES.cpython-313-darwin.so +0 -0
  23. charm/core/crypto/DES3/DES3.c +26 -0
  24. charm/core/crypto/DES3.cpython-313-darwin.so +0 -0
  25. charm/core/crypto/__init__.py +0 -0
  26. charm/core/crypto/cryptobase/XOR.c +80 -0
  27. charm/core/crypto/cryptobase/_counter.c +496 -0
  28. charm/core/crypto/cryptobase/_counter.h +54 -0
  29. charm/core/crypto/cryptobase/block_template.c +900 -0
  30. charm/core/crypto/cryptobase/block_template.h +69 -0
  31. charm/core/crypto/cryptobase/cryptobasemodule.c +220 -0
  32. charm/core/crypto/cryptobase/libtom/tomcrypt.h +90 -0
  33. charm/core/crypto/cryptobase/libtom/tomcrypt_argchk.h +44 -0
  34. charm/core/crypto/cryptobase/libtom/tomcrypt_cfg.h +186 -0
  35. charm/core/crypto/cryptobase/libtom/tomcrypt_cipher.h +941 -0
  36. charm/core/crypto/cryptobase/libtom/tomcrypt_custom.h +556 -0
  37. charm/core/crypto/cryptobase/libtom/tomcrypt_des.c +1912 -0
  38. charm/core/crypto/cryptobase/libtom/tomcrypt_hash.h +407 -0
  39. charm/core/crypto/cryptobase/libtom/tomcrypt_mac.h +496 -0
  40. charm/core/crypto/cryptobase/libtom/tomcrypt_macros.h +435 -0
  41. charm/core/crypto/cryptobase/libtom/tomcrypt_math.h +534 -0
  42. charm/core/crypto/cryptobase/libtom/tomcrypt_misc.h +103 -0
  43. charm/core/crypto/cryptobase/libtom/tomcrypt_pk.h +653 -0
  44. charm/core/crypto/cryptobase/libtom/tomcrypt_pkcs.h +90 -0
  45. charm/core/crypto/cryptobase/libtom/tomcrypt_prng.h +199 -0
  46. charm/core/crypto/cryptobase/stream_template.c +271 -0
  47. charm/core/crypto/cryptobase/strxor.c +229 -0
  48. charm/core/crypto/cryptobase.cpython-313-darwin.so +0 -0
  49. charm/core/engine/__init__.py +5 -0
  50. charm/core/engine/protocol.py +293 -0
  51. charm/core/engine/util.py +174 -0
  52. charm/core/math/__init__.py +0 -0
  53. charm/core/math/elliptic_curve/ecmodule.c +1986 -0
  54. charm/core/math/elliptic_curve/ecmodule.h +230 -0
  55. charm/core/math/elliptic_curve.cpython-313-darwin.so +0 -0
  56. charm/core/math/elliptic_curve.pyi +63 -0
  57. charm/core/math/integer/integermodule.c +2539 -0
  58. charm/core/math/integer/integermodule.h +145 -0
  59. charm/core/math/integer.cpython-313-darwin.so +0 -0
  60. charm/core/math/integer.pyi +76 -0
  61. charm/core/math/pairing/miracl/miracl_config.h +37 -0
  62. charm/core/math/pairing/miracl/miracl_interface.h +118 -0
  63. charm/core/math/pairing/miracl/miracl_interface2.h +126 -0
  64. charm/core/math/pairing/miracl/pairingmodule2.c +2094 -0
  65. charm/core/math/pairing/miracl/pairingmodule2.h +307 -0
  66. charm/core/math/pairing/pairingmodule.c +2230 -0
  67. charm/core/math/pairing/pairingmodule.h +241 -0
  68. charm/core/math/pairing/relic/pairingmodule3.c +1853 -0
  69. charm/core/math/pairing/relic/pairingmodule3.h +233 -0
  70. charm/core/math/pairing/relic/relic_interface.c +1337 -0
  71. charm/core/math/pairing/relic/relic_interface.h +217 -0
  72. charm/core/math/pairing/relic/test_relic.c +171 -0
  73. charm/core/math/pairing.cpython-313-darwin.so +0 -0
  74. charm/core/math/pairing.pyi +69 -0
  75. charm/core/utilities/base64.c +248 -0
  76. charm/core/utilities/base64.h +15 -0
  77. charm/schemes/__init__.py +0 -0
  78. charm/schemes/abenc/__init__.py +0 -0
  79. charm/schemes/abenc/abenc_accountability_jyjxgd20.py +647 -0
  80. charm/schemes/abenc/abenc_bsw07.py +146 -0
  81. charm/schemes/abenc/abenc_ca_cpabe_ar17.py +684 -0
  82. charm/schemes/abenc/abenc_dacmacs_yj14.py +298 -0
  83. charm/schemes/abenc/abenc_lsw08.py +159 -0
  84. charm/schemes/abenc/abenc_maabe_rw15.py +236 -0
  85. charm/schemes/abenc/abenc_maabe_yj14.py +297 -0
  86. charm/schemes/abenc/abenc_tbpre_lww14.py +309 -0
  87. charm/schemes/abenc/abenc_unmcpabe_yahk14.py +223 -0
  88. charm/schemes/abenc/abenc_waters09.py +144 -0
  89. charm/schemes/abenc/abenc_yct14.py +208 -0
  90. charm/schemes/abenc/abenc_yllc15.py +178 -0
  91. charm/schemes/abenc/ac17.py +248 -0
  92. charm/schemes/abenc/bsw07.py +141 -0
  93. charm/schemes/abenc/cgw15.py +277 -0
  94. charm/schemes/abenc/dabe_aw11.py +204 -0
  95. charm/schemes/abenc/dfa_fe12.py +144 -0
  96. charm/schemes/abenc/pk_hve08.py +179 -0
  97. charm/schemes/abenc/waters11.py +143 -0
  98. charm/schemes/aggrsign_MuSig.py +150 -0
  99. charm/schemes/aggrsign_bls.py +267 -0
  100. charm/schemes/blindsig_ps16.py +654 -0
  101. charm/schemes/chamhash_adm05.py +113 -0
  102. charm/schemes/chamhash_rsa_hw09.py +100 -0
  103. charm/schemes/commit/__init__.py +0 -0
  104. charm/schemes/commit/commit_gs08.py +77 -0
  105. charm/schemes/commit/commit_pedersen92.py +53 -0
  106. charm/schemes/encap_bchk05.py +62 -0
  107. charm/schemes/grpsig/__init__.py +0 -0
  108. charm/schemes/grpsig/groupsig_bgls04.py +114 -0
  109. charm/schemes/grpsig/groupsig_bgls04_var.py +115 -0
  110. charm/schemes/hibenc/__init__.py +0 -0
  111. charm/schemes/hibenc/hibenc_bb04.py +105 -0
  112. charm/schemes/hibenc/hibenc_lew11.py +193 -0
  113. charm/schemes/ibenc/__init__.py +0 -0
  114. charm/schemes/ibenc/clpkc_rp03.py +119 -0
  115. charm/schemes/ibenc/ibenc_CW13_z.py +168 -0
  116. charm/schemes/ibenc/ibenc_bb03.py +94 -0
  117. charm/schemes/ibenc/ibenc_bf01.py +121 -0
  118. charm/schemes/ibenc/ibenc_ckrs09.py +120 -0
  119. charm/schemes/ibenc/ibenc_cllww12_z.py +172 -0
  120. charm/schemes/ibenc/ibenc_lsw08.py +120 -0
  121. charm/schemes/ibenc/ibenc_sw05.py +238 -0
  122. charm/schemes/ibenc/ibenc_waters05.py +144 -0
  123. charm/schemes/ibenc/ibenc_waters05_z.py +164 -0
  124. charm/schemes/ibenc/ibenc_waters09.py +107 -0
  125. charm/schemes/ibenc/ibenc_waters09_z.py +147 -0
  126. charm/schemes/joye_scheme.py +106 -0
  127. charm/schemes/lem_scheme.py +207 -0
  128. charm/schemes/pk_fre_ccv11.py +107 -0
  129. charm/schemes/pk_vrf.py +127 -0
  130. charm/schemes/pkenc/__init__.py +0 -0
  131. charm/schemes/pkenc/pkenc_cs98.py +108 -0
  132. charm/schemes/pkenc/pkenc_elgamal85.py +122 -0
  133. charm/schemes/pkenc/pkenc_gm82.py +98 -0
  134. charm/schemes/pkenc/pkenc_paillier99.py +118 -0
  135. charm/schemes/pkenc/pkenc_rabin.py +254 -0
  136. charm/schemes/pkenc/pkenc_rsa.py +186 -0
  137. charm/schemes/pksig/__init__.py +0 -0
  138. charm/schemes/pksig/pksig_CW13_z.py +135 -0
  139. charm/schemes/pksig/pksig_bls04.py +87 -0
  140. charm/schemes/pksig/pksig_boyen.py +156 -0
  141. charm/schemes/pksig/pksig_chch.py +97 -0
  142. charm/schemes/pksig/pksig_chp.py +70 -0
  143. charm/schemes/pksig/pksig_cl03.py +150 -0
  144. charm/schemes/pksig/pksig_cl04.py +87 -0
  145. charm/schemes/pksig/pksig_cllww12_z.py +142 -0
  146. charm/schemes/pksig/pksig_cyh.py +132 -0
  147. charm/schemes/pksig/pksig_dsa.py +76 -0
  148. charm/schemes/pksig/pksig_ecdsa.py +71 -0
  149. charm/schemes/pksig/pksig_hess.py +104 -0
  150. charm/schemes/pksig/pksig_hw.py +110 -0
  151. charm/schemes/pksig/pksig_lamport.py +63 -0
  152. charm/schemes/pksig/pksig_ps01.py +135 -0
  153. charm/schemes/pksig/pksig_ps02.py +124 -0
  154. charm/schemes/pksig/pksig_ps03.py +119 -0
  155. charm/schemes/pksig/pksig_rsa_hw09.py +206 -0
  156. charm/schemes/pksig/pksig_schnorr91.py +77 -0
  157. charm/schemes/pksig/pksig_waters.py +115 -0
  158. charm/schemes/pksig/pksig_waters05.py +121 -0
  159. charm/schemes/pksig/pksig_waters09.py +121 -0
  160. charm/schemes/pre_mg07.py +150 -0
  161. charm/schemes/prenc/pre_afgh06.py +126 -0
  162. charm/schemes/prenc/pre_bbs98.py +123 -0
  163. charm/schemes/prenc/pre_nal16.py +216 -0
  164. charm/schemes/protocol_a01.py +272 -0
  165. charm/schemes/protocol_ao00.py +215 -0
  166. charm/schemes/protocol_cns07.py +274 -0
  167. charm/schemes/protocol_schnorr91.py +125 -0
  168. charm/schemes/sigma1.py +64 -0
  169. charm/schemes/sigma2.py +129 -0
  170. charm/schemes/sigma3.py +126 -0
  171. charm/schemes/threshold/__init__.py +59 -0
  172. charm/schemes/threshold/dkls23_dkg.py +556 -0
  173. charm/schemes/threshold/dkls23_presign.py +1089 -0
  174. charm/schemes/threshold/dkls23_sign.py +761 -0
  175. charm/schemes/threshold/xrpl_wallet.py +967 -0
  176. charm/test/__init__.py +0 -0
  177. charm/test/adapters/__init__.py +0 -0
  178. charm/test/adapters/abenc_adapt_hybrid_test.py +29 -0
  179. charm/test/adapters/dabenc_adapt_hybrid_test.py +56 -0
  180. charm/test/adapters/ibenc_adapt_hybrid_test.py +36 -0
  181. charm/test/adapters/ibenc_adapt_identityhash_test.py +32 -0
  182. charm/test/adapters/kpabenc_adapt_hybrid_test.py +30 -0
  183. charm/test/benchmark/abenc_yllc15_bench.py +92 -0
  184. charm/test/benchmark/benchmark_test.py +148 -0
  185. charm/test/benchmark_threshold.py +260 -0
  186. charm/test/conftest.py +38 -0
  187. charm/test/fuzz/__init__.py +1 -0
  188. charm/test/fuzz/conftest.py +5 -0
  189. charm/test/fuzz/fuzz_policy_parser.py +76 -0
  190. charm/test/fuzz/fuzz_serialization.py +83 -0
  191. charm/test/schemes/__init__.py +0 -0
  192. charm/test/schemes/abenc/__init__.py +0 -0
  193. charm/test/schemes/abenc/abenc_bsw07_test.py +39 -0
  194. charm/test/schemes/abenc/abenc_dacmacs_yj14_test.py +16 -0
  195. charm/test/schemes/abenc/abenc_lsw08_test.py +33 -0
  196. charm/test/schemes/abenc/abenc_maabe_yj14_test.py +16 -0
  197. charm/test/schemes/abenc/abenc_tbpre_lww14_test.py +16 -0
  198. charm/test/schemes/abenc/abenc_waters09_test.py +38 -0
  199. charm/test/schemes/abenc/abenc_yllc15_test.py +74 -0
  200. charm/test/schemes/chamhash_adm05_test.py +31 -0
  201. charm/test/schemes/chamhash_rsa_hw09_test.py +29 -0
  202. charm/test/schemes/commit/__init__.py +0 -0
  203. charm/test/schemes/commit/commit_gs08_test.py +24 -0
  204. charm/test/schemes/commit/commit_pedersen92_test.py +26 -0
  205. charm/test/schemes/dabe_aw11_test.py +45 -0
  206. charm/test/schemes/encap_bchk05_test.py +21 -0
  207. charm/test/schemes/grpsig/__init__.py +0 -0
  208. charm/test/schemes/grpsig/groupsig_bgls04_test.py +35 -0
  209. charm/test/schemes/grpsig/groupsig_bgls04_var_test.py +39 -0
  210. charm/test/schemes/hibenc/__init__.py +0 -0
  211. charm/test/schemes/hibenc/hibenc_bb04_test.py +28 -0
  212. charm/test/schemes/ibenc/__init__.py +0 -0
  213. charm/test/schemes/ibenc/ibenc_bb03_test.py +26 -0
  214. charm/test/schemes/ibenc/ibenc_bf01_test.py +24 -0
  215. charm/test/schemes/ibenc/ibenc_ckrs09_test.py +25 -0
  216. charm/test/schemes/ibenc/ibenc_lsw08_test.py +31 -0
  217. charm/test/schemes/ibenc/ibenc_sw05_test.py +32 -0
  218. charm/test/schemes/ibenc/ibenc_waters05_test.py +31 -0
  219. charm/test/schemes/ibenc/ibenc_waters09_test.py +27 -0
  220. charm/test/schemes/pk_vrf_test.py +29 -0
  221. charm/test/schemes/pkenc/__init__.py +0 -0
  222. charm/test/schemes/pkenc_test.py +255 -0
  223. charm/test/schemes/pksig/__init__.py +0 -0
  224. charm/test/schemes/pksig_test.py +376 -0
  225. charm/test/schemes/rsa_alg_test.py +340 -0
  226. charm/test/schemes/threshold_test.py +1792 -0
  227. charm/test/serialize/__init__.py +0 -0
  228. charm/test/serialize/serialize_test.py +40 -0
  229. charm/test/toolbox/__init__.py +0 -0
  230. charm/test/toolbox/conversion_test.py +30 -0
  231. charm/test/toolbox/ecgroup_test.py +53 -0
  232. charm/test/toolbox/integer_arithmetic_test.py +441 -0
  233. charm/test/toolbox/paddingschemes_test.py +238 -0
  234. charm/test/toolbox/policy_parser_stress_test.py +969 -0
  235. charm/test/toolbox/secretshare_test.py +28 -0
  236. charm/test/toolbox/symcrypto_test.py +108 -0
  237. charm/test/toolbox/test_policy_expression.py +16 -0
  238. charm/test/vectors/__init__.py +1 -0
  239. charm/test/vectors/test_bls_vectors.py +289 -0
  240. charm/test/vectors/test_pedersen_vectors.py +315 -0
  241. charm/test/vectors/test_schnorr_vectors.py +368 -0
  242. charm/test/zkp_compiler/__init__.py +9 -0
  243. charm/test/zkp_compiler/benchmark_zkp.py +258 -0
  244. charm/test/zkp_compiler/test_and_proof.py +240 -0
  245. charm/test/zkp_compiler/test_batch_verify.py +248 -0
  246. charm/test/zkp_compiler/test_dleq_proof.py +264 -0
  247. charm/test/zkp_compiler/test_or_proof.py +231 -0
  248. charm/test/zkp_compiler/test_proof_serialization.py +121 -0
  249. charm/test/zkp_compiler/test_range_proof.py +241 -0
  250. charm/test/zkp_compiler/test_representation_proof.py +325 -0
  251. charm/test/zkp_compiler/test_schnorr_proof.py +221 -0
  252. charm/test/zkp_compiler/test_thread_safety.py +169 -0
  253. charm/test/zkp_compiler/test_zkp_parser.py +139 -0
  254. charm/toolbox/ABEnc.py +26 -0
  255. charm/toolbox/ABEncMultiAuth.py +66 -0
  256. charm/toolbox/ABEnumeric.py +800 -0
  257. charm/toolbox/Commit.py +24 -0
  258. charm/toolbox/DFA.py +89 -0
  259. charm/toolbox/FSA.py +1254 -0
  260. charm/toolbox/Hash.py +39 -0
  261. charm/toolbox/IBEnc.py +62 -0
  262. charm/toolbox/IBSig.py +64 -0
  263. charm/toolbox/PKEnc.py +66 -0
  264. charm/toolbox/PKSig.py +56 -0
  265. charm/toolbox/PREnc.py +32 -0
  266. charm/toolbox/ZKProof.py +289 -0
  267. charm/toolbox/__init__.py +0 -0
  268. charm/toolbox/bitstring.py +49 -0
  269. charm/toolbox/broadcast.py +220 -0
  270. charm/toolbox/conversion.py +100 -0
  271. charm/toolbox/eccurve.py +149 -0
  272. charm/toolbox/ecgroup.py +143 -0
  273. charm/toolbox/enum.py +60 -0
  274. charm/toolbox/hash_module.py +91 -0
  275. charm/toolbox/integergroup.py +323 -0
  276. charm/toolbox/iterate.py +22 -0
  277. charm/toolbox/matrixops.py +76 -0
  278. charm/toolbox/mpc_utils.py +296 -0
  279. charm/toolbox/msp.py +175 -0
  280. charm/toolbox/mta.py +985 -0
  281. charm/toolbox/node.py +120 -0
  282. charm/toolbox/ot/__init__.py +22 -0
  283. charm/toolbox/ot/base_ot.py +374 -0
  284. charm/toolbox/ot/dpf.py +642 -0
  285. charm/toolbox/ot/mpfss.py +228 -0
  286. charm/toolbox/ot/ot_extension.py +589 -0
  287. charm/toolbox/ot/silent_ot.py +378 -0
  288. charm/toolbox/paddingschemes.py +423 -0
  289. charm/toolbox/paddingschemes_test.py +238 -0
  290. charm/toolbox/pairingcurves.py +85 -0
  291. charm/toolbox/pairinggroup.py +186 -0
  292. charm/toolbox/policy_expression_spec.py +70 -0
  293. charm/toolbox/policytree.py +189 -0
  294. charm/toolbox/reCompiler.py +346 -0
  295. charm/toolbox/redundancyschemes.py +65 -0
  296. charm/toolbox/schemebase.py +188 -0
  297. charm/toolbox/secretshare.py +104 -0
  298. charm/toolbox/secretutil.py +174 -0
  299. charm/toolbox/securerandom.py +73 -0
  300. charm/toolbox/sigmaprotocol.py +46 -0
  301. charm/toolbox/specialprimes.py +45 -0
  302. charm/toolbox/symcrypto.py +279 -0
  303. charm/toolbox/threshold_sharing.py +553 -0
  304. charm/toolbox/xmlserialize.py +94 -0
  305. charm/toolbox/zknode.py +105 -0
  306. charm/zkp_compiler/__init__.py +89 -0
  307. charm/zkp_compiler/and_proof.py +460 -0
  308. charm/zkp_compiler/batch_verify.py +324 -0
  309. charm/zkp_compiler/dleq_proof.py +423 -0
  310. charm/zkp_compiler/or_proof.py +305 -0
  311. charm/zkp_compiler/range_proof.py +417 -0
  312. charm/zkp_compiler/representation_proof.py +466 -0
  313. charm/zkp_compiler/schnorr_proof.py +273 -0
  314. charm/zkp_compiler/thread_safe.py +150 -0
  315. charm/zkp_compiler/zk_demo.py +489 -0
  316. charm/zkp_compiler/zkp_factory.py +330 -0
  317. charm/zkp_compiler/zkp_generator.py +370 -0
  318. charm/zkp_compiler/zkparser.py +269 -0
  319. charm_crypto_framework-0.61.1.dist-info/METADATA +337 -0
  320. charm_crypto_framework-0.61.1.dist-info/RECORD +323 -0
  321. charm_crypto_framework-0.61.1.dist-info/WHEEL +5 -0
  322. charm_crypto_framework-0.61.1.dist-info/licenses/LICENSE.txt +165 -0
  323. 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
+ """