├── .gitignore
├── CITATION.bib
├── README.md
├── motivation
├── dynamic
│ ├── inf_sub_pydantic.py
│ ├── inf_sub_pytypes.py
│ ├── inf_sub_typeguard.py
│ └── inf_sub_typical.py
└── static
│ ├── .pyre_configuration
│ ├── inf_sub_static.py
│ └── pyright_unsound.py
└── typing_machines
├── __init__.py
├── abstract_machines
├── __init__.py
└── turing_machine.py
├── app.py
├── compilers
├── __init__.py
├── compiler_g.py
└── compiler_r.py
├── examples
├── __init__.py
└── machines.py
└── experiment
├── __init__.py
└── stack_size_experiment.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### Python template
3 | # Byte-compiled / optimized / DLL files
4 | __pycache__/
5 | *.py[cod]
6 | *$py.class
7 |
8 | # C extensions
9 | *.so
10 |
11 | # Distribution / packaging
12 | .Python
13 | env/
14 | build/
15 | develop-eggs/
16 | dist/
17 | downloads/
18 | eggs/
19 | .eggs/
20 | lib/
21 | lib64/
22 | parts/
23 | sdist/
24 | var/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .coverage
43 | .coverage.*
44 | .cache
45 | nosetests.xml
46 | coverage.xml
47 | *,cover
48 | .hypothesis/
49 |
50 | # Translations
51 | *.mo
52 | *.pot
53 |
54 | # Django stuff:
55 | *.log
56 | local_settings.py
57 |
58 | # Flask stuff:
59 | instance/
60 | .webassets-cache
61 |
62 | # Scrapy stuff:
63 | .scrapy
64 |
65 | # Sphinx documentation
66 | docs/_build/
67 |
68 | # PyBuilder
69 | target/
70 |
71 | # IPython Notebook
72 | .ipynb_checkpoints
73 |
74 | # pyenv
75 | .python-version
76 |
77 | # celery beat schedule file
78 | celerybeat-schedule
79 |
80 | # dotenv
81 | .env
82 |
83 | # virtualenv
84 | venv/
85 | ENV/
86 |
87 | # Spyder project settings
88 | .spyderproject
89 |
90 | # Rope project settings
91 | .ropeproject
92 | ### VirtualEnv template
93 | # Virtualenv
94 | # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/
95 | [Bb]in
96 | [Ii]nclude
97 | [Ll]ib
98 | [Ll]ib64
99 | [Ll]ocal
100 | [Ss]cripts
101 | pyvenv.cfg
102 | .venv
103 | pip-selfcheck.json
104 |
105 | ### JetBrains template
106 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
107 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
108 |
109 | # User-specific stuff
110 | .idea/**/workspace.xml
111 | .idea/**/tasks.xml
112 | .idea/**/usage.statistics.xml
113 | .idea/**/dictionaries
114 | .idea/**/shelf
115 |
116 | # AWS User-specific
117 | .idea/**/aws.xml
118 |
119 | # Generated files
120 | .idea/**/contentModel.xml
121 |
122 | # Sensitive or high-churn files
123 | .idea/**/dataSources/
124 | .idea/**/dataSources.ids
125 | .idea/**/dataSources.local.xml
126 | .idea/**/sqlDataSources.xml
127 | .idea/**/dynamic.xml
128 | .idea/**/uiDesigner.xml
129 | .idea/**/dbnavigator.xml
130 |
131 | # Gradle
132 | .idea/**/gradle.xml
133 | .idea/**/libraries
134 |
135 | # Gradle and Maven with auto-import
136 | # When using Gradle or Maven with auto-import, you should exclude module files,
137 | # since they will be recreated, and may cause churn. Uncomment if using
138 | # auto-import.
139 | # .idea/artifacts
140 | # .idea/compiler.xml
141 | # .idea/jarRepositories.xml
142 | # .idea/modules.xml
143 | # .idea/*.iml
144 | # .idea/modules
145 | # *.iml
146 | # *.ipr
147 |
148 | # CMake
149 | cmake-build-*/
150 |
151 | # Mongo Explorer plugin
152 | .idea/**/mongoSettings.xml
153 |
154 | # File-based project format
155 | *.iws
156 |
157 | # IntelliJ
158 | out/
159 |
160 | # mpeltonen/sbt-idea plugin
161 | .idea_modules/
162 |
163 | # JIRA plugin
164 | atlassian-ide-plugin.xml
165 |
166 | # Cursive Clojure plugin
167 | .idea/replstate.xml
168 |
169 | # SonarLint plugin
170 | .idea/sonarlint/
171 |
172 | # Crashlytics plugin (for Android Studio and IntelliJ)
173 | com_crashlytics_export_strings.xml
174 | crashlytics.properties
175 | crashlytics-build.properties
176 | fabric.properties
177 |
178 | # Editor-based Rest Client
179 | .idea/httpRequests
180 |
181 | # Android studio 3.1+ serialized cache file
182 | .idea/caches/build_file_checksums.ser
183 |
184 | # idea folder, uncomment if you don't need it
185 | .idea
186 |
187 | typeshed-main
188 | .mypy_cache
189 | .pyre
190 |
--------------------------------------------------------------------------------
/CITATION.bib:
--------------------------------------------------------------------------------
1 | @misc{https://doi.org/10.48550/arxiv.2208.14755,
2 | doi = {10.48550/ARXIV.2208.14755},
3 | url = {https://arxiv.org/abs/2208.14755},
4 | author = {Roth, Ori},
5 | keywords = {Programming Languages (cs.PL), FOS: Computer and information sciences, FOS: Computer and information sciences},
6 | title = {Python Type Hints are Turing Complete},
7 | publisher = {arXiv},
8 | year = {2022},
9 | copyright = {Creative Commons Attribution 4.0 International}
10 | }
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Python Type Hints are Turing Complete
2 |
3 | [](https://doi.org/10.5281/zenodo.7004898)
4 |
5 | We present a reduction from Turing machines to Python type hints. The Turing machine accepts its input if and only if
6 | the Python program is correctly typed.
7 |
8 | ## How did it happen?
9 |
10 | [Python enhancement proposal (PEP) 484](https://peps.python.org/pep-0484/)
11 | added optional type hints to the Python programming language. One of the features of the proposed type system is
12 | [nominal subtyping with variance](https://peps.python.org/pep-0484/#covariance-and-contravariance). Radu Grigore showed
13 | that [this particular form of subtyping is Turing complete](https://arxiv.org/abs/1605.05274)
14 | by describing a reduction from Turing machines to class tables. This project applies Radu's construction with Python
15 | type hints.
16 |
17 | ## How can I try it?
18 |
19 | Here is one possible script (also found in `typing_machines/app.py`):
20 |
21 | ```python
22 | from typing_machines.app import * # import application
23 | with open("example.py", "w") as python_file: # write palindromes machine and input "abbabba"
24 | python_file.write(encode(Algorithm.Grigore, palindromes, "abbabba"))
25 | sleep(1) # wait for write operation
26 | with Popen(["mypy", "example.py"]) as p: # run mypy
27 | retcode = p.wait(timeout=10)
28 | assert retcode == 0 # compiles successfully, "abbabba" is a palindrome
29 | with open("example.py", "w") as python_file: # write palindromes machine and input "abbbaba"
30 | python_file.write(encode(Algorithm.Grigore, palindromes, "abbbaba"))
31 | sleep(1) # wait for write operation
32 | with Popen(["mypy", "example.py"]) as p: # run mypy
33 | retcode = p.wait(timeout=10)
34 | assert retcode != 0 # does not compile, "abbbaba" is not a palindrome
35 | ```
36 |
37 | The `palindromes` Turing machine is defined in `typing_machines/examples/machines.py`. You can add new machines in this
38 | file.
39 |
40 | ## Wait, so `mypy` can get into an infinite loop?
41 |
42 | Kind of. As with many other compilers, the subtyping algorithm implemented in `mypy` is recursive, so, recursion +
43 | infinite loop = *stack overflow*. In fact, even this simple program makes `mypy` throw a segmentation fault:
44 |
45 | ```python
46 | from typing import TypeVar, Generic
47 | T = TypeVar("T", contravariant=True)
48 | class N(Generic[T]): ...
49 | class C(N[N["C"]]): ...
50 | _: N[C] = C()
51 | ```
52 |
53 | This is *not* a `mypy` bug. The problem of verifying Python type hints is undecidable, so `mypy` getting stuck for
54 | certain programs is unavoidable.
55 |
56 | ## What's new?
57 |
58 | We introduce an alternative construction that is supposed to compile much faster for large inputs. You can try the new
59 | construction by using `Algorithm.Roth` instead of
60 | `Algorithm.Grigore` in the script above.
61 |
62 | ## Is my type checker in danger?
63 |
64 | We simulate Turing machines and infinite loops at the type level using contravariant type parameters. Thus, your type
65 | checker can get into an infinite loop using our methods only if it supports variance as described in PEP 484.
66 |
67 | | Type Checker | Discipline | Supports Variance? |
68 | |-------------------|------------|--------------------------|
69 | | Mypy 0.991 | static |
● |
70 | | Pyre 0.9.17 | static | ● |
71 | | Pyright 1.1.279 | static | ◐ |
72 | | Pytype 2022.11.10 | static | ○ |
73 | | Pyanalyze 0.8.0 | static | ○ |
74 | | Pydantic 1.10.2 | dynamic | ○ |
75 | | Pytypes 1.0b10 | dynamic | ○ |
76 | | Typeguard 2.13.3 | dynamic | ○ |
77 | | Typical 2.8.0 | dynamic | ○ |
78 |
79 | The programs used to obtain these results are found in `motivation/static` and
80 | `motivation/dynamic`.
81 |
82 | Note that Pyright is *unsound*, which means that it reports errors for
83 | correctly-typed programs, so its variance support is only partial.
84 | For example, the code in `motivation/static/pyright_unsound.py` is correctly
85 | typed, Mypy and Pyre report no error when checking the file, but Pyright does
86 | report an error.
87 |
--------------------------------------------------------------------------------
/motivation/dynamic/inf_sub_pydantic.py:
--------------------------------------------------------------------------------
1 | # Python version: 3.9.5
2 |
3 | # Pydantic version: 1.10.2
4 | # Site: https://pydantic-docs.helpmanual.io/
5 | # Verdict: no error
6 | # Reason: from looking at the source, variance is probably not supported
7 |
8 | from typing import TypeVar, Generic
9 | from pydantic import BaseModel
10 | from pydantic.generics import GenericModel
11 | Z = TypeVar("Z", contravariant=True)
12 | class N(GenericModel, Generic[Z]): ...
13 | class C(N[N["C"]]): ...
14 | class A(BaseModel): x: N[C]
15 | A(x=C())
16 |
--------------------------------------------------------------------------------
/motivation/dynamic/inf_sub_pytypes.py:
--------------------------------------------------------------------------------
1 | # Python version: 3.9.5
2 |
3 | # Pytypes version: 1.0b10
4 | # Site: https://pypi.org/project/pytypes/
5 | # Verdict: run time error
6 | # Reason: refers to isinstance which does not support generic types (only when using resolve_fw_decl?)
7 |
8 | from typing import TypeVar, Generic
9 | from pytypes import is_subtype, resolve_fw_decl
10 | Z = TypeVar("Z", contravariant=True)
11 | class N(Generic[Z]): ...
12 | class C(N[N["C"]]): ...
13 | assert not is_subtype(resolve_fw_decl(C), resolve_fw_decl(N[C]))
14 |
--------------------------------------------------------------------------------
/motivation/dynamic/inf_sub_typeguard.py:
--------------------------------------------------------------------------------
1 | # Python version: 3.9.5
2 |
3 | # Typeguard version: 2.13.3
4 | # Site: https://typeguard.readthedocs.io
5 | # Verdict: no error
6 | # Reason: from looking at the source, variance is probably not supported
7 |
8 | from typing import TypeVar, Generic, Any
9 | from typeguard import typechecked
10 | Z = TypeVar("Z", contravariant=True)
11 | class N(Generic[Z]): ...
12 | X = TypeVar("X")
13 | class C(Generic[X], N[N["C[C[X]]"]]): ...
14 | _: N[C[Any]] = C[Any]()
15 | @typechecked
16 | def f(_: N[C[Any]]): ...
17 | f(C[Any]())
18 |
--------------------------------------------------------------------------------
/motivation/dynamic/inf_sub_typical.py:
--------------------------------------------------------------------------------
1 | # Python version: 3.9.5
2 |
3 | # Typical version: 2.8.0
4 | # Site: https://python-typical.org/
5 | # Verdict: run time error
6 | # Reason: refers to isinstance which does not support generic types
7 |
8 | from typing import TypeVar, Generic, Any
9 | import typic
10 | Z = TypeVar("Z", contravariant=True)
11 | class N(Generic[Z]): ...
12 | X = TypeVar("X")
13 | class C(Generic[X], N[N["C[C[X]]"]]): ...
14 | _: N[C[Any]] = C[Any]()
15 | @typic.al
16 | def f(x: N[C[Any]]): ...
17 | f(C())
18 |
--------------------------------------------------------------------------------
/motivation/static/.pyre_configuration:
--------------------------------------------------------------------------------
1 | {
2 | "site_package_search_strategy": "pep561",
3 | "source_directories": [
4 | "."
5 | ],
6 | "typeshed": "../../typeshed-main"
7 | }
8 |
--------------------------------------------------------------------------------
/motivation/static/inf_sub_static.py:
--------------------------------------------------------------------------------
1 | # Python version: 3.9.5
2 |
3 | # Mypy version: 0.991
4 | # Site: http://mypy-lang.org/
5 | # Verdict: segmentation fault
6 |
7 | # Pyre version: 0.9.17
8 | # Note: download typeshed-main (https://github.com/python/typeshed) and place it in motivation/
9 | # Site: https://pyre-check.org/
10 | # Verdict: internal error
11 |
12 | # Pyright version: 1.1.279
13 | # Site: https://github.com/microsoft/pyright
14 | # Verdict: type error
15 | # Reason: ???
16 |
17 | # Pytype version: 2022.11.10
18 | # Site: https://google.github.io/pytype/
19 | # Verdict: variance not supported
20 |
21 | # Pyanalyze version: 0.8.0
22 | # Site: https://pyanalyze.readthedocs.io
23 | # Verdict: no error
24 | # Reason: from looking at the source, variance is probably not supported
25 |
26 | from typing import TypeVar, Generic, Any
27 | Z = TypeVar("Z", contravariant=True)
28 | class N(Generic[Z]): ...
29 | X = TypeVar("X")
30 | class C(Generic[X], N[N["C[C[X]]"]]): ...
31 | _: N[C[Any]] = C[Any]() # infinite subtyping
32 |
--------------------------------------------------------------------------------
/motivation/static/pyright_unsound.py:
--------------------------------------------------------------------------------
1 | # This file contains a subtyping machine that recognizes palindromes and its input "abbabba", a palindrome.
2 | # Mypy and Pyre compile this file with no errors, as expected.
3 | # Pyright reports a type error, meaning that it is unsound.
4 |
5 | from typing import TypeVar, Generic
6 | T = TypeVar("T", contravariant=True)
7 | class Z: ...
8 | class N(Generic[T]): ...
9 | class ML(Generic[T]): ...
10 | class MR(Generic[T]): ...
11 | class L_a(Generic[T]): ...
12 | class L_c(Generic[T]): ...
13 | class L_b(Generic[T]): ...
14 | class L___TAPE_END__(Generic[T]): ...
15 | class QLR_q0(Generic[T]): ...
16 | class QLR_q1(Generic[T]): ...
17 | class QLR_q2(Generic[T]): ...
18 | class QLR_q8(Generic[T]): ...
19 | class QLR_q3(Generic[T]): ...
20 | class QLR_q4(Generic[T]): ...
21 | class QLR_q5(Generic[T]): ...
22 | class QLR_q6(Generic[T]): ...
23 | class QLR_q7(Generic[T]): ...
24 | class QRL_q0(Generic[T]): ...
25 | class QRL_q1(Generic[T]): ...
26 | class QRL_q2(Generic[T]): ...
27 | class QRL_q8(Generic[T]): ...
28 | class QRL_q3(Generic[T]): ...
29 | class QRL_q4(Generic[T]): ...
30 | class QRL_q5(Generic[T]): ...
31 | class QRL_q6(Generic[T]): ...
32 | class QRL_q7(Generic[T]): ...
33 | class E(Generic[T], QLR_q0["N[QRW_q0[E[E[T]]]]"], QLR_q1["N[QRW_q1[E[E[T]]]]"], QLR_q2["N[QRW_q2[E[E[T]]]]"], QLR_q8["N[QRW_q8[E[E[T]]]]"], QLR_q3["N[QRW_q3[E[E[T]]]]"], QLR_q4["N[QRW_q4[E[E[T]]]]"], QLR_q5["N[QRW_q5[E[E[T]]]]"], QLR_q6["N[QRW_q6[E[E[T]]]]"], QLR_q7["N[QRW_q7[E[E[T]]]]"], QRL_q0["N[QLW_q0[E[E[T]]]]"], QRL_q1["N[QLW_q1[E[E[T]]]]"], QRL_q2["N[QLW_q2[E[E[T]]]]"], QRL_q8["N[QLW_q8[E[E[T]]]]"], QRL_q3["N[QLW_q3[E[E[T]]]]"], QRL_q4["N[QLW_q4[E[E[T]]]]"], QRL_q5["N[QLW_q5[E[E[T]]]]"], QRL_q6["N[QLW_q6[E[E[T]]]]"], QRL_q7["N[QLW_q7[E[E[T]]]]"]): ...
34 | class QLW_q0(Generic[T], ML["N[QL_q0[T]]"], MR["N[QLW_q0[MR[N[T]]]]"], L_a["N[QLW_q0[L_a[N[T]]]]"], L_c["N[QLW_q0[L_c[N[T]]]]"], L_b["N[QLW_q0[L_b[N[T]]]]"], L___TAPE_END__["N[QLW_q0[L___TAPE_END__[N[T]]]]"], E["QLR_q0[N[T]]"]): ...
35 | class QRW_q0(Generic[T], MR["N[QR_q0[T]]"], ML["N[QRW_q0[ML[N[T]]]]"], L_a["N[QRW_q0[L_a[N[T]]]]"], L_c["N[QRW_q0[L_c[N[T]]]]"], L_b["N[QRW_q0[L_b[N[T]]]]"], L___TAPE_END__["N[QRW_q0[L___TAPE_END__[N[T]]]]"], E["QRL_q0[N[T]]"]): ...
36 | class QLW_q1(Generic[T], ML["N[QL_q1[T]]"], MR["N[QLW_q1[MR[N[T]]]]"], L_a["N[QLW_q1[L_a[N[T]]]]"], L_c["N[QLW_q1[L_c[N[T]]]]"], L_b["N[QLW_q1[L_b[N[T]]]]"], L___TAPE_END__["N[QLW_q1[L___TAPE_END__[N[T]]]]"], E["QLR_q1[N[T]]"]): ...
37 | class QRW_q1(Generic[T], MR["N[QR_q1[T]]"], ML["N[QRW_q1[ML[N[T]]]]"], L_a["N[QRW_q1[L_a[N[T]]]]"], L_c["N[QRW_q1[L_c[N[T]]]]"], L_b["N[QRW_q1[L_b[N[T]]]]"], L___TAPE_END__["N[QRW_q1[L___TAPE_END__[N[T]]]]"], E["QRL_q1[N[T]]"]): ...
38 | class QLW_q2(Generic[T], ML["N[QL_q2[T]]"], MR["N[QLW_q2[MR[N[T]]]]"], L_a["N[QLW_q2[L_a[N[T]]]]"], L_c["N[QLW_q2[L_c[N[T]]]]"], L_b["N[QLW_q2[L_b[N[T]]]]"], L___TAPE_END__["N[QLW_q2[L___TAPE_END__[N[T]]]]"], E["QLR_q2[N[T]]"]): ...
39 | class QRW_q2(Generic[T], MR["N[QR_q2[T]]"], ML["N[QRW_q2[ML[N[T]]]]"], L_a["N[QRW_q2[L_a[N[T]]]]"], L_c["N[QRW_q2[L_c[N[T]]]]"], L_b["N[QRW_q2[L_b[N[T]]]]"], L___TAPE_END__["N[QRW_q2[L___TAPE_END__[N[T]]]]"], E["QRL_q2[N[T]]"]): ...
40 | class QLW_q8(Generic[T], ML["N[QL_q8[T]]"], MR["N[QLW_q8[MR[N[T]]]]"], L_a["N[QLW_q8[L_a[N[T]]]]"], L_c["N[QLW_q8[L_c[N[T]]]]"], L_b["N[QLW_q8[L_b[N[T]]]]"], L___TAPE_END__["N[QLW_q8[L___TAPE_END__[N[T]]]]"], E["E[Z]"]): ...
41 | class QRW_q8(Generic[T], MR["N[QR_q8[T]]"], ML["N[QRW_q8[ML[N[T]]]]"], L_a["N[QRW_q8[L_a[N[T]]]]"], L_c["N[QRW_q8[L_c[N[T]]]]"], L_b["N[QRW_q8[L_b[N[T]]]]"], L___TAPE_END__["N[QRW_q8[L___TAPE_END__[N[T]]]]"], E["E[Z]"]): ...
42 | class QLW_q3(Generic[T], ML["N[QL_q3[T]]"], MR["N[QLW_q3[MR[N[T]]]]"], L_a["N[QLW_q3[L_a[N[T]]]]"], L_c["N[QLW_q3[L_c[N[T]]]]"], L_b["N[QLW_q3[L_b[N[T]]]]"], L___TAPE_END__["N[QLW_q3[L___TAPE_END__[N[T]]]]"], E["QLR_q3[N[T]]"]): ...
43 | class QRW_q3(Generic[T], MR["N[QR_q3[T]]"], ML["N[QRW_q3[ML[N[T]]]]"], L_a["N[QRW_q3[L_a[N[T]]]]"], L_c["N[QRW_q3[L_c[N[T]]]]"], L_b["N[QRW_q3[L_b[N[T]]]]"], L___TAPE_END__["N[QRW_q3[L___TAPE_END__[N[T]]]]"], E["QRL_q3[N[T]]"]): ...
44 | class QLW_q4(Generic[T], ML["N[QL_q4[T]]"], MR["N[QLW_q4[MR[N[T]]]]"], L_a["N[QLW_q4[L_a[N[T]]]]"], L_c["N[QLW_q4[L_c[N[T]]]]"], L_b["N[QLW_q4[L_b[N[T]]]]"], L___TAPE_END__["N[QLW_q4[L___TAPE_END__[N[T]]]]"], E["QLR_q4[N[T]]"]): ...
45 | class QRW_q4(Generic[T], MR["N[QR_q4[T]]"], ML["N[QRW_q4[ML[N[T]]]]"], L_a["N[QRW_q4[L_a[N[T]]]]"], L_c["N[QRW_q4[L_c[N[T]]]]"], L_b["N[QRW_q4[L_b[N[T]]]]"], L___TAPE_END__["N[QRW_q4[L___TAPE_END__[N[T]]]]"], E["QRL_q4[N[T]]"]): ...
46 | class QLW_q5(Generic[T], ML["N[QL_q5[T]]"], MR["N[QLW_q5[MR[N[T]]]]"], L_a["N[QLW_q5[L_a[N[T]]]]"], L_c["N[QLW_q5[L_c[N[T]]]]"], L_b["N[QLW_q5[L_b[N[T]]]]"], L___TAPE_END__["N[QLW_q5[L___TAPE_END__[N[T]]]]"], E["QLR_q5[N[T]]"]): ...
47 | class QRW_q5(Generic[T], MR["N[QR_q5[T]]"], ML["N[QRW_q5[ML[N[T]]]]"], L_a["N[QRW_q5[L_a[N[T]]]]"], L_c["N[QRW_q5[L_c[N[T]]]]"], L_b["N[QRW_q5[L_b[N[T]]]]"], L___TAPE_END__["N[QRW_q5[L___TAPE_END__[N[T]]]]"], E["QRL_q5[N[T]]"]): ...
48 | class QLW_q6(Generic[T], ML["N[QL_q6[T]]"], MR["N[QLW_q6[MR[N[T]]]]"], L_a["N[QLW_q6[L_a[N[T]]]]"], L_c["N[QLW_q6[L_c[N[T]]]]"], L_b["N[QLW_q6[L_b[N[T]]]]"], L___TAPE_END__["N[QLW_q6[L___TAPE_END__[N[T]]]]"], E["QLR_q6[N[T]]"]): ...
49 | class QRW_q6(Generic[T], MR["N[QR_q6[T]]"], ML["N[QRW_q6[ML[N[T]]]]"], L_a["N[QRW_q6[L_a[N[T]]]]"], L_c["N[QRW_q6[L_c[N[T]]]]"], L_b["N[QRW_q6[L_b[N[T]]]]"], L___TAPE_END__["N[QRW_q6[L___TAPE_END__[N[T]]]]"], E["QRL_q6[N[T]]"]): ...
50 | class QLW_q7(Generic[T], ML["N[QL_q7[T]]"], MR["N[QLW_q7[MR[N[T]]]]"], L_a["N[QLW_q7[L_a[N[T]]]]"], L_c["N[QLW_q7[L_c[N[T]]]]"], L_b["N[QLW_q7[L_b[N[T]]]]"], L___TAPE_END__["N[QLW_q7[L___TAPE_END__[N[T]]]]"], E["QLR_q7[N[T]]"]): ...
51 | class QRW_q7(Generic[T], MR["N[QR_q7[T]]"], ML["N[QRW_q7[ML[N[T]]]]"], L_a["N[QRW_q7[L_a[N[T]]]]"], L_c["N[QRW_q7[L_c[N[T]]]]"], L_b["N[QRW_q7[L_b[N[T]]]]"], L___TAPE_END__["N[QRW_q7[L___TAPE_END__[N[T]]]]"], E["QRL_q7[N[T]]"]): ...
52 | class QL_q0(Generic[T], L_a["N[QLW_q1[L_c[N[MR[N[T]]]]]]"], L_b["N[QLW_q5[L_c[N[MR[N[T]]]]]]"], L___TAPE_END__["N[QLW_q8[L___TAPE_END__[N[L_c[N[MR[N[T]]]]]]]]"], L_c["N[QLW_q8[L_c[N[MR[N[T]]]]]]"]): ...
53 | class QR_q0(Generic[T], L_a["N[QRW_q1[MR[N[L_c[N[T]]]]]]"], L_b["N[QRW_q5[MR[N[L_c[N[T]]]]]]"], L___TAPE_END__["N[QRW_q8[L___TAPE_END__[N[MR[N[L_c[N[T]]]]]]]]"], L_c["N[QRW_q8[MR[N[L_c[N[T]]]]]]"]): ...
54 | class QL_q1(Generic[T], L_a["N[QLW_q2[L_a[N[MR[N[T]]]]]]"], L_b["N[QLW_q2[L_b[N[MR[N[T]]]]]]"], L___TAPE_END__["N[QLW_q8[L___TAPE_END__[N[L_c[N[MR[N[T]]]]]]]]"], L_c["N[QLW_q8[L_c[N[MR[N[T]]]]]]"]): ...
55 | class QR_q1(Generic[T], L_a["N[QRW_q2[MR[N[L_a[N[T]]]]]]"], L_b["N[QRW_q2[MR[N[L_b[N[T]]]]]]"], L___TAPE_END__["N[QRW_q8[L___TAPE_END__[N[MR[N[L_c[N[T]]]]]]]]"], L_c["N[QRW_q8[MR[N[L_c[N[T]]]]]]"]): ...
56 | class QL_q2(Generic[T], L_a["N[QLW_q2[L_a[N[MR[N[T]]]]]]"], L_b["N[QLW_q2[L_b[N[MR[N[T]]]]]]"], L___TAPE_END__["N[QLW_q3[L___TAPE_END__[N[ML[N[L_c[N[T]]]]]]]]"], L_c["N[QLW_q3[ML[N[L_c[N[T]]]]]]"]): ...
57 | class QR_q2(Generic[T], L_a["N[QRW_q2[MR[N[L_a[N[T]]]]]]"], L_b["N[QRW_q2[MR[N[L_b[N[T]]]]]]"], L___TAPE_END__["N[QRW_q3[L___TAPE_END__[N[L_c[N[ML[N[T]]]]]]]]"], L_c["N[QRW_q3[L_c[N[ML[N[T]]]]]]"]): ...
58 | class QL_q8(Generic[T]): ...
59 | class QR_q8(Generic[T]): ...
60 | class QL_q3(Generic[T], L_a["N[QLW_q4[ML[N[L_c[N[T]]]]]]"]): ...
61 | class QR_q3(Generic[T], L_a["N[QRW_q4[L_c[N[ML[N[T]]]]]]"]): ...
62 | class QL_q4(Generic[T], L_a["N[QLW_q4[ML[N[L_a[N[T]]]]]]"], L_b["N[QLW_q4[ML[N[L_b[N[T]]]]]]"], L___TAPE_END__["N[QLW_q0[L___TAPE_END__[N[L_c[N[MR[N[T]]]]]]]]"], L_c["N[QLW_q0[L_c[N[MR[N[T]]]]]]"]): ...
63 | class QR_q4(Generic[T], L_a["N[QRW_q4[L_a[N[ML[N[T]]]]]]"], L_b["N[QRW_q4[L_b[N[ML[N[T]]]]]]"], L___TAPE_END__["N[QRW_q0[L___TAPE_END__[N[MR[N[L_c[N[T]]]]]]]]"], L_c["N[QRW_q0[MR[N[L_c[N[T]]]]]]"]): ...
64 | class QL_q5(Generic[T], L_a["N[QLW_q6[L_a[N[MR[N[T]]]]]]"], L_b["N[QLW_q6[L_b[N[MR[N[T]]]]]]"], L___TAPE_END__["N[QLW_q8[L___TAPE_END__[N[L_c[N[MR[N[T]]]]]]]]"], L_c["N[QLW_q8[L_c[N[MR[N[T]]]]]]"]): ...
65 | class QR_q5(Generic[T], L_a["N[QRW_q6[MR[N[L_a[N[T]]]]]]"], L_b["N[QRW_q6[MR[N[L_b[N[T]]]]]]"], L___TAPE_END__["N[QRW_q8[L___TAPE_END__[N[MR[N[L_c[N[T]]]]]]]]"], L_c["N[QRW_q8[MR[N[L_c[N[T]]]]]]"]): ...
66 | class QL_q6(Generic[T], L_a["N[QLW_q6[L_a[N[MR[N[T]]]]]]"], L_b["N[QLW_q6[L_b[N[MR[N[T]]]]]]"], L___TAPE_END__["N[QLW_q7[L___TAPE_END__[N[ML[N[L_c[N[T]]]]]]]]"], L_c["N[QLW_q7[ML[N[L_c[N[T]]]]]]"]): ...
67 | class QR_q6(Generic[T], L_a["N[QRW_q6[MR[N[L_a[N[T]]]]]]"], L_b["N[QRW_q6[MR[N[L_b[N[T]]]]]]"], L___TAPE_END__["N[QRW_q7[L___TAPE_END__[N[L_c[N[ML[N[T]]]]]]]]"], L_c["N[QRW_q7[L_c[N[ML[N[T]]]]]]"]): ...
68 | class QL_q7(Generic[T], L_b["N[QLW_q4[ML[N[L_c[N[T]]]]]]"]): ...
69 | class QR_q7(Generic[T], L_b["N[QRW_q4[L_c[N[ML[N[T]]]]]]"]): ...
70 | _: E[E[Z]] = QRW_q0[L___TAPE_END__[N[L_a[N[L_b[N[L_b[N[L_a[N[L_b[N[L_b[N[L_a[N[MR[N[L___TAPE_END__[N[E[E[Z]]]]]]]]]]]]]]]]]]]]]]]()
71 |
--------------------------------------------------------------------------------
/typing_machines/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OriRoth/python-typing-machines/cedb8529a3b0907abdf49c30d0f72ec6e2fdd90b/typing_machines/__init__.py
--------------------------------------------------------------------------------
/typing_machines/abstract_machines/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OriRoth/python-typing-machines/cedb8529a3b0907abdf49c30d0f72ec6e2fdd90b/typing_machines/abstract_machines/__init__.py
--------------------------------------------------------------------------------
/typing_machines/abstract_machines/turing_machine.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 | from enum import Enum
3 | from functools import cached_property
4 | from typing import List
5 |
6 |
7 | class Direction(Enum):
8 | """
9 | Machine head move direction.
10 | """
11 | LEFT = 1
12 | RIGHT = 2
13 |
14 |
15 | @dataclass
16 | class Transition:
17 | """
18 | Turing machine transition.
19 | When the machine is in state `source_state` and
20 | and the current tape cell contains the letter `read_letter`,
21 | the machine moves to state `target_state`, writes letter
22 | `write_letter` in the current cell, and moves the machine
23 | head according to `move_direction`.
24 | """
25 | source_state: str
26 | read_letter: str
27 | target_state: str
28 | write_letter: str
29 | move_direction: Direction
30 |
31 |
32 | @dataclass
33 | class TuringMachine:
34 | """
35 | Turing machine specifications.
36 | Accepts the input word immediately when reaching its single accepting state.
37 | """
38 | BLANK = "@"
39 | initial_state: str
40 | termination_state: str
41 | transitions: List[Transition]
42 |
43 | @cached_property
44 | def alphabet(self) -> List[str]:
45 | """
46 | Returns the machine alphabet, i.e., the set of machine letters.
47 | """
48 | alphabet = []
49 | for transition in self.transitions:
50 | if transition.read_letter not in alphabet and transition.read_letter != TuringMachine.BLANK:
51 | alphabet.append(transition.read_letter)
52 | if transition.write_letter not in alphabet and transition.write_letter != TuringMachine.BLANK:
53 | alphabet.append(transition.write_letter)
54 | return alphabet
55 |
56 | @cached_property
57 | def states(self) -> List[str]:
58 | """
59 | Returns the set of machine states.
60 | """
61 | states = [self.initial_state]
62 | for transition in self.transitions:
63 | if transition.source_state not in states:
64 | states.append(transition.source_state)
65 | if transition.target_state not in states:
66 | states.append(transition.target_state)
67 | if self.termination_state not in states:
68 | states.append(self.termination_state)
69 | return states
70 |
--------------------------------------------------------------------------------
/typing_machines/app.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 | from os import remove
3 | from subprocess import Popen
4 | from time import sleep
5 | from typing import Union, List
6 |
7 | from typing_machines.abstract_machines.turing_machine import TuringMachine
8 | from typing_machines.compilers.compiler_g import compile_g, compile_query_g
9 | from typing_machines.compilers.compiler_r import compile_r, compile_query_r
10 | from typing_machines.examples.machines import palindromes
11 |
12 |
13 | class Algorithm(Enum):
14 | """
15 | Supported encoding algorithms by author name.
16 | """
17 | Grigore = 1
18 | Roth = 2
19 |
20 |
21 | def encode_machine(algorithm: Algorithm, machine: TuringMachine) -> str:
22 | """
23 | Encode a Turing machine as a Python class table with given algorithm.
24 | """
25 | if algorithm == Algorithm.Grigore:
26 | return compile_g(machine)
27 | elif algorithm == Algorithm.Roth:
28 | return compile_r(machine)
29 | else:
30 | raise Exception(f"unrecognized algorithm {algorithm}")
31 |
32 |
33 | def encode_query(algorithm: Algorithm, machine: TuringMachine, input_word: Union[str, List[str]]) -> str:
34 | """
35 | Encode an input word as a Python subtyping query with given algorithm.
36 | """
37 | if algorithm == Algorithm.Grigore:
38 | return compile_query_g(input_word, machine)
39 | elif algorithm == Algorithm.Roth:
40 | return compile_query_r(input_word, machine)
41 | else:
42 | raise Exception(f"unrecognized algorithm {algorithm}")
43 |
44 |
45 | def encode(algorithm: Algorithm, machine: TuringMachine, input_word: Union[str, List[str]]) -> str:
46 | """
47 | Encode a Turing machine and its input using Python typing hints with given algorithm.
48 | """
49 | return encode_machine(algorithm, machine) + "\n" + encode_query(algorithm, machine, input_word)
50 |
51 |
52 | if __name__ == '__main__':
53 | print("Is 'abbabba' a palindrome?")
54 | with open("example.py", "w") as python_file:
55 | python_file.write(encode(Algorithm.Grigore, palindromes, "abbabba"))
56 | sleep(1)
57 | with Popen(["mypy", "example.py"]) as p:
58 | retcode = p.wait(timeout=10)
59 | assert retcode == 0 # abbabba is a palindrome
60 | print("Is 'abbbaba' a palindrome?")
61 | with open("example.py", "w") as python_file:
62 | python_file.write(encode(Algorithm.Grigore, palindromes, "abbbaba"))
63 | sleep(1)
64 | with Popen(["mypy", "example.py"]) as p:
65 | retcode = p.wait(timeout=10)
66 | assert retcode != 0 # abbbaba is not a palindrome
67 | remove("example.py")
68 |
--------------------------------------------------------------------------------
/typing_machines/compilers/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OriRoth/python-typing-machines/cedb8529a3b0907abdf49c30d0f72ec6e2fdd90b/typing_machines/compilers/__init__.py
--------------------------------------------------------------------------------
/typing_machines/compilers/compiler_g.py:
--------------------------------------------------------------------------------
1 | from typing import List, Union
2 |
3 | from typing_machines.abstract_machines.turing_machine import TuringMachine, Direction
4 |
5 |
6 | def _render_type(*types: str, stringify_argument=True) -> str:
7 | assert len(types) > 0
8 | if len(types) == 1:
9 | return types[0]
10 | quotation: str = "\"" if stringify_argument else ""
11 | return f"{types[0]}[{quotation}{_render_type(*types[1:], stringify_argument=False)}{quotation}]"
12 |
13 |
14 | TAPE_END: str = "__TAPE_END__"
15 |
16 |
17 | def compile_g(turing_machine: TuringMachine) -> str:
18 | """
19 | Compiles a Turing machine into Python type hints that simulate it.
20 | Implementation of Grigore's original construction, see here for more details:
21 | https://arxiv.org/abs/1605.05274
22 | """
23 | typing_machine: str = ""
24 | # render imports and type variable
25 | typing_machine += "from typing import TypeVar, Generic\n"
26 | typing_machine += "T = TypeVar(\"T\", contravariant=True)\n"
27 | # render Z / N / ML / MR
28 | typing_machine += "class Z: ...\n"
29 | typing_machine += "class N(Generic[T]): ...\n"
30 | typing_machine += "class ML(Generic[T]): ...\n"
31 | typing_machine += "class MR(Generic[T]): ...\n"
32 | # render L_s
33 | typing_machine += "\n".join(f"class L_{letter}(Generic[T]): ..." for letter in turing_machine.alphabet) + "\n"
34 | typing_machine += f"class L_{TAPE_END}(Generic[T]): ...\n"
35 | # render q_lr / q_rl
36 | typing_machine += "\n".join(f"class QLR_{state}(Generic[T]): ..." for state in turing_machine.states) + "\n"
37 | typing_machine += "\n".join(f"class QRL_{state}(Generic[T]): ..." for state in turing_machine.states) + "\n"
38 | # render E
39 | supers_e: List[str] = []
40 | supers_e += [_render_type(f"QLR_{state}", "N", f"QRW_{state}", "E", "E", "T") for state in turing_machine.states]
41 | supers_e += [_render_type(f"QRL_{state}", "N", f"QLW_{state}", "E", "E", "T") for state in turing_machine.states]
42 | super_clause: str = "" if len(supers_e) == 0 else ", " + ", ".join(supers_e)
43 | typing_machine += f"class E(Generic[T]{super_clause}): ...\n"
44 | # render q_lw / q_rw
45 | for state in turing_machine.states:
46 | supers_lw: List[str] = []
47 | supers_rw: List[str] = []
48 | supers_lw.append(_render_type("ML", "N", f"QL_{state}", "T"))
49 | supers_rw.append(_render_type("MR", "N", f"QR_{state}", "T"))
50 | supers_lw.append(_render_type("MR", "N", f"QLW_{state}", "MR", "N", "T"))
51 | supers_rw.append(_render_type("ML", "N", f"QRW_{state}", "ML", "N", "T"))
52 | supers_lw += [_render_type(f"L_{letter}", "N", f"QLW_{state}", f"L_{letter}", "N", "T")
53 | for letter in turing_machine.alphabet + [TAPE_END]]
54 | supers_rw += [_render_type(f"L_{letter}", "N", f"QRW_{state}", f"L_{letter}", "N", "T")
55 | for letter in turing_machine.alphabet + [TAPE_END]]
56 | if state != turing_machine.termination_state:
57 | supers_lw.append(_render_type("E", f"QLR_{state}", "N", "T"))
58 | supers_rw.append(_render_type("E", f"QRL_{state}", "N", "T"))
59 | else:
60 | supers_lw.append(_render_type("E", "E", "Z"))
61 | supers_rw.append(_render_type("E", "E", "Z"))
62 | super_clause: str = "" if len(supers_lw) == 0 else ", " + ", ".join(supers_lw)
63 | typing_machine += f"class QLW_{state}(Generic[T]{super_clause}): ...\n"
64 | super_clause = "" if len(supers_rw) == 0 else ", " + ", ".join(supers_rw)
65 | typing_machine += f"class QRW_{state}(Generic[T]{super_clause}): ...\n"
66 | # render q_l / q_r
67 | for state in turing_machine.states:
68 | supers_l: List[str] = []
69 | supers_r: List[str] = []
70 | for transition in turing_machine.transitions:
71 | if transition.source_state == state:
72 | if transition.read_letter != TuringMachine.BLANK:
73 | if transition.move_direction == Direction.LEFT:
74 | supers_l.append(_render_type(f"L_{transition.read_letter}", "N",
75 | f"QLW_{transition.target_state}", "ML", "N",
76 | f"L_{transition.write_letter}", "N", "T"))
77 | supers_r.append(_render_type(f"L_{transition.read_letter}", "N",
78 | f"QRW_{transition.target_state}", f"L_{transition.write_letter}",
79 | "N", "ML", "N", "T"))
80 | else:
81 | supers_l.append(_render_type(f"L_{transition.read_letter}", "N",
82 | f"QLW_{transition.target_state}", f"L_{transition.write_letter}",
83 | "N", "MR", "N", "T"))
84 | supers_r.append(_render_type(f"L_{transition.read_letter}", "N",
85 | f"QRW_{transition.target_state}", "MR", "N",
86 | f"L_{transition.write_letter}", "N", "T"))
87 | else:
88 | if transition.move_direction == Direction.LEFT:
89 | supers_l.append(_render_type(f"L_{TAPE_END}", "N", f"QLW_{transition.target_state}",
90 | f"L_{TAPE_END}", "N", "ML", "N",
91 | f"L_{transition.write_letter}", "N", "T"))
92 | supers_r.append(_render_type(f"L_{TAPE_END}", "N", f"QRW_{transition.target_state}",
93 | f"L_{TAPE_END}", "N", f"L_{transition.write_letter}",
94 | "N", "ML", "N", "T"))
95 | else:
96 | supers_l.append(_render_type(f"L_{TAPE_END}", "N", f"QLW_{transition.target_state}",
97 | f"L_{TAPE_END}", "N", f"L_{transition.write_letter}",
98 | "N", "MR", "N", "T"))
99 | supers_r.append(_render_type(f"L_{TAPE_END}", "N", f"QRW_{transition.target_state}",
100 | f"L_{TAPE_END}", "N", "MR", "N",
101 | f"L_{transition.write_letter}", "N", "T"))
102 | super_clause: str = "" if len(supers_l) == 0 else ", " + ", ".join(supers_l)
103 | typing_machine += f"class QL_{state}(Generic[T]{super_clause}): ...\n"
104 | super_clause = "" if len(supers_r) == 0 else ", " + ", ".join(supers_r)
105 | typing_machine += f"class QR_{state}(Generic[T]{super_clause}): ...\n"
106 | return typing_machine.rstrip()
107 |
108 |
109 | def compile_query_g(input_word: Union[str, List[str]],
110 | turing_machine: TuringMachine):
111 | """
112 | Compiles an input word into a variable assignments which invokes a
113 | subtyping query.
114 | """
115 | if isinstance(input_word, str):
116 | return compile_query_g(list(input_word), turing_machine)
117 | input_word = input_word[::-1]
118 | tape: List[str] = []
119 | tape.append(f"QRW_{turing_machine.initial_state}")
120 | tape.append(f"L_{TAPE_END}")
121 | tape.append("N")
122 | for letter in input_word:
123 | tape.append(f"L_{letter}")
124 | tape.append("N")
125 | tape.append("MR")
126 | tape.append("N")
127 | tape.append(f"L_{TAPE_END}")
128 | tape.append("N")
129 | tape.append("E")
130 | tape.append("E")
131 | tape.append("Z")
132 | tape_type: str = "[".join(tape) + "]" * (len(tape) - 1)
133 | return f"_: E[E[Z]] = {tape_type}()"
134 |
--------------------------------------------------------------------------------
/typing_machines/compilers/compiler_r.py:
--------------------------------------------------------------------------------
1 | from typing import List, Union
2 |
3 | from typing_machines.abstract_machines.turing_machine import TuringMachine, Direction
4 |
5 |
6 | def _render_type(*types: str, stringify_argument=True) -> str:
7 | assert len(types) > 0
8 | if len(types) == 1:
9 | return types[0]
10 | quotation: str = "\"" if stringify_argument else ""
11 | return f"{types[0]}[{quotation}{_render_type(*types[1:], stringify_argument=False)}{quotation}]"
12 |
13 |
14 | TAPE_END: str = "__TAPE_END__"
15 |
16 |
17 | def compile_r(turing_machine: TuringMachine) -> str:
18 | """
19 | Compiles a Turing machine into Python type hints that simulate it
20 | in real time.
21 | """
22 | typing_machine: str = ""
23 | # render imports and type variable
24 | typing_machine += "from typing import TypeVar, Generic, Any\n"
25 | typing_machine += "T = TypeVar(\"T\", contravariant=True)\n"
26 | # render Z
27 | typing_machine += "class Z: ...\n"
28 | # render L_s
29 | typing_machine += "\n".join(f"class L_{letter}(Generic[T]): ..." for letter in turing_machine.alphabet) + "\n"
30 | typing_machine += f"class L_{TAPE_END}(Generic[T]): ...\n"
31 | # render q_l / q_r
32 | for state in turing_machine.states:
33 | if state == turing_machine.termination_state:
34 | continue
35 | supers_l = []
36 | supers_r = []
37 | for transition in turing_machine.transitions:
38 | if transition.source_state == state:
39 | if transition.read_letter != TuringMachine.BLANK:
40 | if transition.move_direction == Direction.LEFT:
41 | supers_l.append(_render_type(f"L_{transition.read_letter}", "N",
42 | f"QL_{transition.target_state}",
43 | f"L_{transition.write_letter}", "N", "T"))
44 | supers_r.append(_render_type(f"L_{transition.read_letter}",
45 | f"QWRL_{transition.target_state}",
46 | "N", f"L_{transition.write_letter}", "N", "T"))
47 | else:
48 | supers_l.append(_render_type(f"L_{transition.read_letter}",
49 | f"QWLR_{transition.target_state}",
50 | "N", f"L_{transition.write_letter}", "N", "T"))
51 | supers_r.append(_render_type(f"L_{transition.read_letter}", "N",
52 | f"QR_{transition.target_state}",
53 | f"L_{transition.write_letter}", "N", "T"))
54 | else:
55 | if transition.move_direction == Direction.LEFT:
56 | supers_l.append(_render_type(f"L_{TAPE_END}", f"QLSL_{transition.target_state}",
57 | "N", f"L_{transition.write_letter}", "N", "T"))
58 | supers_r.append(_render_type(f"L_{TAPE_END}", f"QRSL_{transition.target_state}",
59 | "N", f"L_{transition.write_letter}", "N", "T"))
60 | else:
61 | supers_l.append(_render_type(f"L_{TAPE_END}", f"QLSR_{transition.target_state}",
62 | "N", f"L_{transition.write_letter}", "N", "T"))
63 | supers_r.append(_render_type(f"L_{TAPE_END}", f"QRSR_{transition.target_state}",
64 | "N", f"L_{transition.write_letter}", "N", "T"))
65 | super_clause: str = "" if len(supers_l) == 0 else ", " + ", ".join(supers_l)
66 | typing_machine += f"class QL_{state}(Generic[T]{super_clause}): ...\n"
67 | super_clause = "" if len(supers_r) == 0 else ", " + ", ".join(supers_r)
68 | typing_machine += f"class QR_{state}(Generic[T]{super_clause}): ...\n"
69 | supers_hl = []
70 | supers_hr = []
71 | for letter in turing_machine.alphabet + [TAPE_END]:
72 | supers_hl.append(_render_type(f"L_{letter}", "Any"))
73 | supers_hr.append(_render_type(f"L_{letter}", "Any"))
74 | super_clause: str = "" if len(supers_hl) == 0 else ", " + ", ".join(supers_hl)
75 | typing_machine += f"class QL_{turing_machine.termination_state}(Generic[T]{super_clause}): ...\n"
76 | super_clause = "" if len(supers_hr) == 0 else ", " + ", ".join(supers_hr)
77 | typing_machine += f"class QR_{turing_machine.termination_state}(Generic[T]{super_clause}): ...\n"
78 | # render q_wl / q_wr
79 | for state in turing_machine.states:
80 | supers_wl = []
81 | supers_wr = []
82 | for letter in turing_machine.alphabet:
83 | supers_wl.append(_render_type(f"L_{letter}", "N", f"QL_{state}", f"L_{letter}", "N", "T"))
84 | supers_wr.append(_render_type(f"L_{letter}", "N", f"QR_{state}", f"L_{letter}", "N", "T"))
85 | super_clause: str = "" if len(supers_wl) == 0 else ", " + ", ".join(supers_wl)
86 | typing_machine += f"class QWL_{state}(Generic[T]{super_clause}): ...\n"
87 | super_clause = "" if len(supers_wr) == 0 else ", " + ", ".join(supers_wr)
88 | typing_machine += f"class QWR_{state}(Generic[T]{super_clause}): ...\n"
89 | # render q_rl / q_lr / q_wlr / q_wrl / q_lsl / q_rsr / q_lsr / q_rsl
90 | for state in turing_machine.states:
91 | typing_machine += f"class QRL_{state}(Generic[T]): ...\n"
92 | typing_machine += f"class QLR_{state}(Generic[T]): ...\n"
93 | typing_machine += f"class QWLR_{state}(Generic[T]): ...\n"
94 | typing_machine += f"class QWRL_{state}(Generic[T]): ...\n"
95 | typing_machine += f"class QLSL_{state}(Generic[T]): ...\n"
96 | typing_machine += f"class QRSR_{state}(Generic[T]): ...\n"
97 | typing_machine += f"class QLSR_{state}(Generic[T]): ...\n"
98 | typing_machine += f"class QRSL_{state}(Generic[T]): ...\n"
99 | # render N
100 | supers_n = []
101 | for state in turing_machine.states:
102 | supers_n.append(_render_type(f"QWLR_{state}", "N", f"QWR_{state}", "T"))
103 | supers_n.append(_render_type(f"QWRL_{state}", "N", f"QWL_{state}", "T"))
104 | supers_n.append(_render_type(f"QLSL_{state}", f"QRL_{state}", "N", f"L_{TAPE_END}", "N", "T"))
105 | supers_n.append(_render_type(f"QRSR_{state}", f"QLR_{state}", "N", f"L_{TAPE_END}", "N", "T"))
106 | supers_n.append(_render_type(f"QLSR_{state}", "N", f"QWR_{state}", f"L_{TAPE_END}", "N", "T"))
107 | supers_n.append(_render_type(f"QRSL_{state}", "N", f"QWL_{state}", f"L_{TAPE_END}", "N", "T"))
108 | supers_n.append(_render_type(f"QLR_{state}", "N", f"QR_{state}", "T"))
109 | supers_n.append(_render_type(f"QRL_{state}", "N", f"QL_{state}", "T"))
110 | super_clause: str = "" if len(supers_n) == 0 else ", " + ", ".join(supers_n)
111 | typing_machine += f"class N(Generic[T]{super_clause}): ..."
112 | return typing_machine
113 |
114 |
115 | def compile_query_r(input_word: Union[str, List[str]],
116 | turing_machine: TuringMachine):
117 | """
118 | Compiles an input word into a variable assignments which invokes a
119 | subtyping query.
120 | """
121 | if isinstance(input_word, str):
122 | return compile_query_r(list(input_word), turing_machine)
123 | tape: List[str] = []
124 | for letter in input_word:
125 | tape.append(f"L_{letter}")
126 | tape.append("N")
127 | tape.append(f"L_{TAPE_END}")
128 | tape.append("N")
129 | tape.append("Z")
130 | value: str = _render_type(f"QR_{turing_machine.initial_state}", f"L_{TAPE_END}", "N", "Z")
131 | return f"_: {_render_type(*tape)} = {value}()"
132 |
--------------------------------------------------------------------------------
/typing_machines/examples/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OriRoth/python-typing-machines/cedb8529a3b0907abdf49c30d0f72ec6e2fdd90b/typing_machines/examples/__init__.py
--------------------------------------------------------------------------------
/typing_machines/examples/machines.py:
--------------------------------------------------------------------------------
1 | """
2 | Turing machine examples.
3 | """
4 |
5 | from typing_machines.abstract_machines.turing_machine import TuringMachine, Transition, Direction
6 |
7 | anbn: TuringMachine = TuringMachine("q0", "q4", [
8 | Transition("q0", "a", "q1", "c", Direction.RIGHT),
9 | Transition("q1", "a", "q1", "a", Direction.RIGHT),
10 | Transition("q1", "b", "q1", "b", Direction.RIGHT),
11 | Transition("q1", TuringMachine.BLANK, "q2", "c", Direction.LEFT),
12 | Transition("q1", "c", "q2", "c", Direction.LEFT),
13 | Transition("q2", "b", "q3", "c", Direction.LEFT),
14 | Transition("q3", "a", "q3", "a", Direction.LEFT),
15 | Transition("q3", "b", "q3", "b", Direction.LEFT),
16 | Transition("q3", TuringMachine.BLANK, "q0", "c", Direction.RIGHT),
17 | Transition("q3", "c", "q0", "c", Direction.RIGHT),
18 | Transition("q0", TuringMachine.BLANK, "q4", "c", Direction.RIGHT),
19 | Transition("q0", "c", "q4", "c", Direction.RIGHT),
20 | ])
21 | """ Recognizes { a^n b^n | n >=0 } """
22 |
23 | palindromes: TuringMachine = TuringMachine("q0", "q8", [
24 | Transition("q0", "a", "q1", "c", Direction.RIGHT),
25 | Transition("q1", "a", "q2", "a", Direction.RIGHT),
26 | Transition("q1", "b", "q2", "b", Direction.RIGHT),
27 | Transition("q1", TuringMachine.BLANK, "q8", "c", Direction.RIGHT),
28 | Transition("q1", "c", "q8", "c", Direction.RIGHT),
29 | Transition("q2", "a", "q2", "a", Direction.RIGHT),
30 | Transition("q2", "b", "q2", "b", Direction.RIGHT),
31 | Transition("q2", TuringMachine.BLANK, "q3", "c", Direction.LEFT),
32 | Transition("q2", "c", "q3", "c", Direction.LEFT),
33 | Transition("q3", "a", "q4", "c", Direction.LEFT),
34 | Transition("q4", "a", "q4", "a", Direction.LEFT),
35 | Transition("q4", "b", "q4", "b", Direction.LEFT),
36 | Transition("q4", TuringMachine.BLANK, "q0", "c", Direction.RIGHT),
37 | Transition("q4", "c", "q0", "c", Direction.RIGHT),
38 | Transition("q0", "b", "q5", "c", Direction.RIGHT),
39 | Transition("q5", "a", "q6", "a", Direction.RIGHT),
40 | Transition("q5", "b", "q6", "b", Direction.RIGHT),
41 | Transition("q5", TuringMachine.BLANK, "q8", "c", Direction.RIGHT),
42 | Transition("q5", "c", "q8", "c", Direction.RIGHT),
43 | Transition("q6", "a", "q6", "a", Direction.RIGHT),
44 | Transition("q6", "b", "q6", "b", Direction.RIGHT),
45 | Transition("q6", TuringMachine.BLANK, "q7", "c", Direction.LEFT),
46 | Transition("q6", "c", "q7", "c", Direction.LEFT),
47 | Transition("q7", "b", "q4", "c", Direction.LEFT),
48 | Transition("q0", TuringMachine.BLANK, "q8", "c", Direction.RIGHT),
49 | Transition("q0", "c", "q8", "c", Direction.RIGHT),
50 | ])
51 | """ Recognizes { w | w is a palindrome over {a,b} } """
52 |
--------------------------------------------------------------------------------
/typing_machines/experiment/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OriRoth/python-typing-machines/cedb8529a3b0907abdf49c30d0f72ec6e2fdd90b/typing_machines/experiment/__init__.py
--------------------------------------------------------------------------------
/typing_machines/experiment/stack_size_experiment.py:
--------------------------------------------------------------------------------
1 | from os import remove
2 | from random import Random
3 | from resource import RLIMIT_STACK, setrlimit
4 | from subprocess import Popen, DEVNULL
5 | from time import sleep
6 | from typing import Callable, List, Tuple, Iterable
7 |
8 | import matplotlib.pyplot as plt
9 |
10 | from typing_machines.app import encode, Algorithm
11 | from typing_machines.examples.machines import palindromes
12 |
13 |
14 | def binary_search(le: Callable[[int], bool]) -> int:
15 | """
16 | Finds a natural number according to the given "lower equals" predicate.
17 | Returns -1 if no number is found.
18 | """
19 | l: int = -1
20 | u: int = 1
21 | while le(u):
22 | l = u
23 | u *= 2
24 | u -= 1
25 | while l < u:
26 | m: int = (l + u) // 2 + (l + u) % 2
27 | if le(m):
28 | l = m
29 | else:
30 | u = m - 1
31 | return l
32 |
33 |
34 | def get_random_palindrome(n: int) -> str:
35 | """
36 | Returns a random palindrome over {a, b} of length n.
37 | Always returns the same word for a given n.
38 | """
39 | random: Random = Random(n)
40 | palindrome: str = ""
41 | while n > 0:
42 | l: str = "a" if random.getrandbits(1) else "b"
43 | palindrome = l + palindrome + l
44 | n -= 1
45 | return palindrome
46 |
47 |
48 | def get_stack_size(algorithm: Algorithm, input_word: str) -> int:
49 | """
50 | Get the call stack size mypy requires to compile the palindromes typing machine with the given
51 | algorithm and input palindrome.
52 | """
53 |
54 | def compiles(n: int) -> bool:
55 | with open("test.py", "w") as python_file:
56 | python_file.write(encode(algorithm, palindromes, input_word))
57 | sleep(1)
58 | stack_size: int = (n + 5) * 1000000
59 | with Popen(["mypy", "test.py"], preexec_fn=lambda: setrlimit(RLIMIT_STACK, (stack_size, stack_size)),
60 | stdout=DEVNULL, stderr=DEVNULL) as p:
61 | retcode = p.wait(timeout=10)
62 | remove("test.py")
63 | return retcode != 0
64 |
65 | depth: int = binary_search(compiles)
66 | depth = 5 if depth == -1 else depth + 5
67 | return depth
68 |
69 |
70 | def run_experiment(algorithm: Algorithm, input_lengths: Iterable[int]) -> List[Tuple[int, int]]:
71 | """
72 | Find mypy stack sizes for given algorithm and input lengths.
73 | """
74 | results: List[Tuple[int, int]] = []
75 | for n in input_lengths:
76 | s: int = get_stack_size(algorithm, get_random_palindrome(n))
77 | results.append((n * 2, s))
78 | print(f"mypy requires {s}M stack size with algorithm {algorithm.name} and palindrome of length {n * 2}")
79 | return results
80 |
81 |
82 | if __name__ == '__main__':
83 | grigore_results: List[Tuple[int, int]] = run_experiment(Algorithm.Grigore, range(5, 9))
84 | print("Grigore's results:")
85 | for n, s in grigore_results:
86 | print(f"Grigore\t{n}\t{s}")
87 | roth_results: List[Tuple[int, int]] = run_experiment(Algorithm.Roth, range(5, 46, 5))
88 | print("Roth's results:")
89 | for n, s in roth_results:
90 | print(f"Roth\t{n}\t{s}")
91 | plt.plot([n for n, _ in grigore_results], [s for _, s in grigore_results], color="blue", label="Grigore")
92 | plt.scatter([n for n, _ in grigore_results], [s for _, s in grigore_results], color="blue", marker="o")
93 | plt.plot([n for n, _ in roth_results], [s for _, s in roth_results], color="green", label="Roth")
94 | plt.scatter([n for n, _ in roth_results], [s for _, s in roth_results], color="green", marker="x")
95 | plt.legend(loc="upper right")
96 | plt.show()
97 |
--------------------------------------------------------------------------------