lean-python 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lean_python/__init__.py +6 -0
- lean_python/cli.py +50 -0
- lean_python/embed.py +131 -0
- lean_python/io.py +15 -0
- lean_python/io_externs.py +1192 -0
- lean_python/marshal.py +727 -0
- lean_python/models.py +297 -0
- lean_python/runtime.py +1400 -0
- lean_python/sync.py +265 -0
- lean_python/types.py +33 -0
- lean_python-0.1.0.dist-info/METADATA +231 -0
- lean_python-0.1.0.dist-info/RECORD +15 -0
- lean_python-0.1.0.dist-info/WHEEL +4 -0
- lean_python-0.1.0.dist-info/entry_points.txt +2 -0
- lean_python-0.1.0.dist-info/licenses/LICENSE +202 -0
lean_python/__init__.py
ADDED
lean_python/cli.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""``lean-py``: the command line of the Python package.
|
|
2
|
+
|
|
3
|
+
usage: lean-py sync --package <dir> [--lean-python <path>]
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
USAGE = """usage: lean-py <command> [parameters]
|
|
11
|
+
|
|
12
|
+
commands:
|
|
13
|
+
sync Check the Lean blocks of a package and generate their Python modules
|
|
14
|
+
|
|
15
|
+
lean-py sync
|
|
16
|
+
--package <dir> The Python package (the directory with __init__.py) (required)
|
|
17
|
+
--lean-python <path> The Lean CLI (default: $LEAN_PYTHON_CLI, then `lean-python` on PATH)
|
|
18
|
+
|
|
19
|
+
exit codes: 0 success, 252 invalid parameters, 254 input that cannot be converted, 255 other errors
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def main(argv=None):
|
|
24
|
+
argv = sys.argv[1:] if argv is None else argv
|
|
25
|
+
if not argv or argv[0] in ("help", "--help", "-h"):
|
|
26
|
+
print(USAGE, end="")
|
|
27
|
+
return 0
|
|
28
|
+
if argv[0] != "sync":
|
|
29
|
+
print(f"lean-py: unknown command {argv[0]!r} (see `lean-py help`)", file=sys.stderr)
|
|
30
|
+
return 252
|
|
31
|
+
p = argparse.ArgumentParser(prog="lean-py sync", add_help=False, exit_on_error=False)
|
|
32
|
+
p.add_argument("--package", required=True)
|
|
33
|
+
p.add_argument("--lean-python")
|
|
34
|
+
try:
|
|
35
|
+
args = p.parse_args(argv[1:])
|
|
36
|
+
except (argparse.ArgumentError, SystemExit) as e:
|
|
37
|
+
print(f"lean-py: {e}", file=sys.stderr)
|
|
38
|
+
return 252
|
|
39
|
+
from lean_python import sync
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
sync.sync_package(args.package, cli=args.lean_python or os.environ.get("LEAN_PYTHON_CLI"))
|
|
43
|
+
except sync.SyncError as e:
|
|
44
|
+
print(f"lean-py: {e}", file=sys.stderr)
|
|
45
|
+
return e.code
|
|
46
|
+
return 0
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
if __name__ == "__main__":
|
|
50
|
+
sys.exit(main())
|
lean_python/embed.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""``lean(name, source)``: Lean code in a Python module.
|
|
2
|
+
|
|
3
|
+
``lean-py sync`` checks the proofs of every block of a package with Lake and writes the Python it
|
|
4
|
+
generates to ``<package>/_lean/``. At run time ``lean(...)`` only imports that module, after
|
|
5
|
+
checking that it was generated from the same source; Lean is not needed to run the package.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import hashlib
|
|
9
|
+
import importlib
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import subprocess
|
|
13
|
+
import sys
|
|
14
|
+
import typing
|
|
15
|
+
from dataclasses import dataclass, field
|
|
16
|
+
|
|
17
|
+
GENERATOR_VERSION = "1"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class LeanBlockStale(RuntimeError):
|
|
21
|
+
"""The generated module of a block is missing or was generated from a different source."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class Block:
|
|
26
|
+
module: str
|
|
27
|
+
name: str
|
|
28
|
+
source: str
|
|
29
|
+
imports: tuple
|
|
30
|
+
uses: tuple
|
|
31
|
+
models: tuple
|
|
32
|
+
file: str
|
|
33
|
+
line: int
|
|
34
|
+
key: str = field(default="")
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def qualname(self):
|
|
38
|
+
return f"{self.module}:{self.name}"
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def stem(self):
|
|
42
|
+
return "__".join([*self.module.split("."), self.name])
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def lean_module(self):
|
|
46
|
+
return ["LeanPythonEmbed", *self.module.split("."), self.name]
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def top_package(self):
|
|
50
|
+
return self.module.split(".")[0]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def block_key(block):
|
|
54
|
+
from lean_python import models
|
|
55
|
+
|
|
56
|
+
model_sources = models.modules_source(list(block.models)) if block.models else {}
|
|
57
|
+
data = {
|
|
58
|
+
"module": block.module,
|
|
59
|
+
"name": block.name,
|
|
60
|
+
"source": block.source,
|
|
61
|
+
"imports": list(block.imports),
|
|
62
|
+
"uses": list(block.uses),
|
|
63
|
+
"models": model_sources,
|
|
64
|
+
"generator": GENERATOR_VERSION,
|
|
65
|
+
}
|
|
66
|
+
return hashlib.sha256(json.dumps(data, sort_keys=True).encode()).hexdigest()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
_recording = None
|
|
70
|
+
_registries = {}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class _Placeholder:
|
|
74
|
+
"""What `lean(...)` returns while `lean-py sync` records the blocks: names are `Any`."""
|
|
75
|
+
|
|
76
|
+
def __getattr__(self, name):
|
|
77
|
+
return typing.Any
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _registry(pkg):
|
|
81
|
+
reg = _registries.get(pkg)
|
|
82
|
+
if reg is None:
|
|
83
|
+
try:
|
|
84
|
+
base = sys.modules[pkg].__path__[0]
|
|
85
|
+
except (KeyError, AttributeError, IndexError):
|
|
86
|
+
raise LeanBlockStale(f"lean(...) must be called from a module of a package (got {pkg!r})") from None
|
|
87
|
+
path = os.path.join(base, "_lean", "blocks.json")
|
|
88
|
+
try:
|
|
89
|
+
with open(path, encoding="utf-8") as f:
|
|
90
|
+
reg = json.load(f)
|
|
91
|
+
except FileNotFoundError:
|
|
92
|
+
reg = {}
|
|
93
|
+
_registries[pkg] = reg
|
|
94
|
+
return reg
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def lean(name, source, *, imports=(), uses=(), models=()):
|
|
98
|
+
"""Defines the Lean block ``name`` of the calling module; returns its generated module."""
|
|
99
|
+
frame = sys._getframe(1)
|
|
100
|
+
block = Block(
|
|
101
|
+
module=frame.f_globals["__name__"],
|
|
102
|
+
name=name,
|
|
103
|
+
source=source,
|
|
104
|
+
imports=tuple(imports),
|
|
105
|
+
uses=tuple(uses),
|
|
106
|
+
models=tuple(models),
|
|
107
|
+
file=frame.f_code.co_filename,
|
|
108
|
+
line=frame.f_lineno,
|
|
109
|
+
)
|
|
110
|
+
if _recording is not None:
|
|
111
|
+
_recording.append(block)
|
|
112
|
+
return _Placeholder()
|
|
113
|
+
block.key = block_key(block)
|
|
114
|
+
pkg = block.top_package
|
|
115
|
+
if _registry(pkg).get(block.qualname) != block.key:
|
|
116
|
+
if os.environ.get("LEAN_PYTHON_AUTO_SYNC") == "1":
|
|
117
|
+
r = subprocess.run(
|
|
118
|
+
[sys.executable, "-m", "lean_python.cli", "sync", "--package", sys.modules[pkg].__path__[0]],
|
|
119
|
+
check=False,
|
|
120
|
+
env={**os.environ, "LEAN_PYTHON_AUTO_SYNC": "0"},
|
|
121
|
+
)
|
|
122
|
+
if r.returncode != 0:
|
|
123
|
+
raise LeanBlockStale(f"`lean-py sync` of {pkg} failed (exit {r.returncode})")
|
|
124
|
+
_registries.pop(pkg, None)
|
|
125
|
+
importlib.invalidate_caches()
|
|
126
|
+
if _registry(pkg).get(block.qualname) != block.key:
|
|
127
|
+
raise LeanBlockStale(
|
|
128
|
+
f"the Lean block {name!r} of {block.module} has changed since `lean-py sync` generated it;"
|
|
129
|
+
" run `lean-py sync` (or set LEAN_PYTHON_AUTO_SYNC=1)"
|
|
130
|
+
)
|
|
131
|
+
return importlib.import_module(f"{pkg}._lean.{block.stem}")
|
lean_python/io.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Running a Lean `main`-like IO action."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from lean_python.io_externs import lean_io_result_show_error
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def run_main(f):
|
|
9
|
+
"""Runs the IR function of an `IO Unit` action as Lean's C `main` does; returns the exit code."""
|
|
10
|
+
r = f()
|
|
11
|
+
sys.stdout.flush()
|
|
12
|
+
if r.tag == 0:
|
|
13
|
+
return 0
|
|
14
|
+
lean_io_result_show_error(r)
|
|
15
|
+
return 1
|