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.
@@ -0,0 +1,6 @@
1
+ """lean-python: Lean 4 code compiled to Python."""
2
+
3
+ from lean_python.embed import LeanBlockStale, lean
4
+ from lean_python.types import Nat
5
+
6
+ __all__ = ["LeanBlockStale", "Nat", "lean"]
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