pyfun-lang 0.0.1__tar.gz

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 (49) hide show
  1. pyfun_lang-0.0.1/.github/workflows/wheels.yml +122 -0
  2. pyfun_lang-0.0.1/.gitignore +20 -0
  3. pyfun_lang-0.0.1/CLAUDE.md +106 -0
  4. pyfun_lang-0.0.1/Cargo.lock +7 -0
  5. pyfun_lang-0.0.1/Cargo.toml +22 -0
  6. pyfun_lang-0.0.1/DESIGN.md +1640 -0
  7. pyfun_lang-0.0.1/LICENSE +201 -0
  8. pyfun_lang-0.0.1/NOTICE +15 -0
  9. pyfun_lang-0.0.1/PKG-INFO +426 -0
  10. pyfun_lang-0.0.1/README.md +406 -0
  11. pyfun_lang-0.0.1/ROADMAP.md +91 -0
  12. pyfun_lang-0.0.1/build-lsp.ps1 +43 -0
  13. pyfun_lang-0.0.1/editors/vscode/.gitignore +3 -0
  14. pyfun_lang-0.0.1/editors/vscode/README.md +142 -0
  15. pyfun_lang-0.0.1/editors/vscode/extension.js +46 -0
  16. pyfun_lang-0.0.1/editors/vscode/language-configuration.json +22 -0
  17. pyfun_lang-0.0.1/editors/vscode/package.json +174 -0
  18. pyfun_lang-0.0.1/editors/vscode/pyfun.tmLanguage.json +147 -0
  19. pyfun_lang-0.0.1/examples/hello.pyfun +622 -0
  20. pyfun_lang-0.0.1/examples/modules/geometry.pyfun +7 -0
  21. pyfun_lang-0.0.1/examples/modules/main.pyfun +32 -0
  22. pyfun_lang-0.0.1/examples/modules/store.pyfun +9 -0
  23. pyfun_lang-0.0.1/pyproject.toml +27 -0
  24. pyfun_lang-0.0.1/rust-toolchain.toml +5 -0
  25. pyfun_lang-0.0.1/src/ast/mod.rs +583 -0
  26. pyfun_lang-0.0.1/src/desugar.rs +277 -0
  27. pyfun_lang-0.0.1/src/diagnostics/mod.rs +79 -0
  28. pyfun_lang-0.0.1/src/lexer/mod.rs +1538 -0
  29. pyfun_lang-0.0.1/src/lexer/token.rs +171 -0
  30. pyfun_lang-0.0.1/src/lib.rs +235 -0
  31. pyfun_lang-0.0.1/src/lowering/mod.rs +3115 -0
  32. pyfun_lang-0.0.1/src/lsp/json.rs +376 -0
  33. pyfun_lang-0.0.1/src/lsp/mod.rs +2723 -0
  34. pyfun_lang-0.0.1/src/lsp/resolve.rs +853 -0
  35. pyfun_lang-0.0.1/src/main.rs +457 -0
  36. pyfun_lang-0.0.1/src/parser/ast.rs +690 -0
  37. pyfun_lang-0.0.1/src/parser/mod.rs +1980 -0
  38. pyfun_lang-0.0.1/src/project/mod.rs +750 -0
  39. pyfun_lang-0.0.1/src/python_emitter/mod.rs +692 -0
  40. pyfun_lang-0.0.1/src/repl.rs +312 -0
  41. pyfun_lang-0.0.1/src/types/mod.rs +6149 -0
  42. pyfun_lang-0.0.1/tests/cli.rs +230 -0
  43. pyfun_lang-0.0.1/tests/compile.rs +2576 -0
  44. pyfun_lang-0.0.1/tests/lsp.rs +634 -0
  45. pyfun_lang-0.0.1/tests/project.rs +778 -0
  46. pyfun_lang-0.0.1/tests/repl.rs +98 -0
  47. pyfun_lang-0.0.1/tests/roundtrip.rs +680 -0
  48. pyfun_lang-0.0.1/tests/run.rs +92 -0
  49. pyfun_lang-0.0.1/tests/typecheck.rs +2863 -0
@@ -0,0 +1,122 @@
1
+ # Build platform wheels for `pyfun-lang` and publish to PyPI on a version tag.
2
+ #
3
+ # Publishing uses PyPI Trusted Publishing (OIDC) — there is NO API token to manage.
4
+ # One-time setup before the first release: on PyPI, add a Trusted Publisher for this
5
+ # repo pointing at workflow `wheels.yml` and environment `pypi`
6
+ # (https://docs.pypi.org/trusted-publishers/). Until that exists, the build jobs still
7
+ # run on every push/PR; only the final `release` job needs it, and it runs only on tags.
8
+ #
9
+ # The wheel matrix is deliberately lean (the common student/dev platforms). Any platform
10
+ # without a wheel falls back to the sdist and builds from source (needs Rust). To widen
11
+ # coverage, regenerate the full skeleton with: maturin generate-ci github
12
+ name: wheels
13
+
14
+ on:
15
+ push:
16
+ branches: [main]
17
+ tags: ['*']
18
+ pull_request:
19
+ workflow_dispatch:
20
+
21
+ permissions:
22
+ contents: read
23
+
24
+ concurrency:
25
+ group: wheels-${{ github.ref }}
26
+ cancel-in-progress: true
27
+
28
+ jobs:
29
+ linux:
30
+ runs-on: ubuntu-22.04
31
+ strategy:
32
+ matrix:
33
+ target: [x86_64, aarch64]
34
+ steps:
35
+ - uses: actions/checkout@v6
36
+ - name: Build wheels
37
+ uses: PyO3/maturin-action@v1
38
+ with:
39
+ target: ${{ matrix.target }}
40
+ args: --release --out dist
41
+ sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
42
+ manylinux: auto
43
+ - uses: actions/upload-artifact@v6
44
+ with:
45
+ name: wheels-linux-${{ matrix.target }}
46
+ path: dist
47
+
48
+ windows:
49
+ runs-on: windows-latest
50
+ steps:
51
+ - uses: actions/checkout@v6
52
+ - name: Build wheels
53
+ uses: PyO3/maturin-action@v1
54
+ with:
55
+ target: x64
56
+ args: --release --out dist
57
+ sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
58
+ - uses: actions/upload-artifact@v6
59
+ with:
60
+ name: wheels-windows-x64
61
+ path: dist
62
+
63
+ macos:
64
+ runs-on: ${{ matrix.runner }}
65
+ strategy:
66
+ matrix:
67
+ include:
68
+ - runner: macos-15-intel
69
+ target: x86_64
70
+ - runner: macos-latest
71
+ target: aarch64
72
+ steps:
73
+ - uses: actions/checkout@v6
74
+ - name: Build wheels
75
+ uses: PyO3/maturin-action@v1
76
+ with:
77
+ target: ${{ matrix.target }}
78
+ args: --release --out dist
79
+ sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
80
+ - uses: actions/upload-artifact@v6
81
+ with:
82
+ name: wheels-macos-${{ matrix.target }}
83
+ path: dist
84
+
85
+ sdist:
86
+ runs-on: ubuntu-latest
87
+ steps:
88
+ - uses: actions/checkout@v6
89
+ - name: Build sdist
90
+ uses: PyO3/maturin-action@v1
91
+ with:
92
+ command: sdist
93
+ args: --out dist
94
+ - uses: actions/upload-artifact@v6
95
+ with:
96
+ name: wheels-sdist
97
+ path: dist
98
+
99
+ release:
100
+ name: Publish to PyPI
101
+ runs-on: ubuntu-latest
102
+ if: startsWith(github.ref, 'refs/tags/')
103
+ needs: [linux, windows, macos, sdist]
104
+ environment: pypi
105
+ permissions:
106
+ id-token: write # OIDC token for Trusted Publishing
107
+ attestations: write # sign the build artifacts
108
+ contents: read
109
+ steps:
110
+ - uses: actions/download-artifact@v7
111
+ with:
112
+ pattern: wheels-*
113
+ path: dist
114
+ merge-multiple: true
115
+ - name: Generate artifact attestation
116
+ uses: actions/attest-build-provenance@v2
117
+ with:
118
+ subject-path: 'dist/*'
119
+ - name: Publish to PyPI
120
+ uses: pypa/gh-action-pypi-publish@release/v1
121
+ with:
122
+ packages-dir: dist
@@ -0,0 +1,20 @@
1
+ # Rust / Cargo
2
+ /target
3
+ # Secondary target dir used while the LSP server holds a lock on /target.
4
+ /target-test
5
+
6
+ # Cargo.lock is committed for binaries (this crate ships a `pyfun` bin).
7
+
8
+ # Python packaging (maturin wheels + build artifacts)
9
+ /dist
10
+ *.egg-info/
11
+
12
+ # Editor / OS
13
+ *.swp
14
+ .DS_Store
15
+ .vscode/
16
+
17
+ # VS Code extension client (installed locally, not vendored)
18
+ editors/vscode/node_modules/
19
+ editors/vscode/*.vsix
20
+ .claude/worktrees/
@@ -0,0 +1,106 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## What Pyfun is (one paragraph)
6
+
7
+ **Pyfun is an F#-inspired, functional-first language for the Python ecosystem** — immutable-by-default,
8
+ expression-oriented, type-inferred ADTs, exhaustive matching, **curried functions + pipe `|>`**,
9
+ **first-class effect tracking**, plus two F# showcase features: **computation expressions**
10
+ (`async {}`/`seq {}`/`result {}`) and **units of measure** — whose **compiler is written in Rust**
11
+ and **compiles to readable Python**, interoperating with the Python ecosystem directly. Safety is
12
+ F#-level because **the Rust compiler is the gatekeeper**: all type/effect/exhaustiveness/immutability/
13
+ unit checks run before any Python is emitted (the TypeScript/Elm model). Full rationale, semantics,
14
+ and the interop model are in `DESIGN.md`.
15
+
16
+ ## Status
17
+
18
+ MVP showcase complete and runnable (`pyfun run`). Implemented:
19
+
20
+ - **Core FP:** ADTs, records, tuples, exhaustive `match` (deep/Maranget usefulness with concrete
21
+ witnesses), curried functions, `|>`/`<|`/`>>`/`<<`, operator sections, HM type inference with
22
+ let-generalization, implicit + mutual recursion.
23
+ - **F# showcase:** computation expressions (`async`/`seq`/`result` + user-defined builders); units of
24
+ measure (derived-measure aliases, unit-aware `sqrt`/`cbrt`); first-class **inferred** effects
25
+ (`let pure` assertion, multi-label `io`/`async`, `->{label}` annotations on declared arrows).
26
+ - **Mutation:** blocks (`let mut`/`<-`), immutable-by-default, closure capture via `nonlocal`/`global`.
27
+ - **Python interop:** general `extern` FFI (effectful-by-default boundary); `try e : Result a Exception`.
28
+ - **Stdlib (module-qualified):** `List`/`Set`/`Map`/`Option`/`Result`/`Seq`/`String`.
29
+ - **Strings/literals:** `f"..."` interpolation, raw `r"..."`, triple-quoted, escapes; scientific
30
+ notation, digit separators, hex/octal/binary, `**`, `%`, `//`, unary minus, chained comparisons.
31
+ - **Advanced patterns:** active patterns, list/sequence patterns, or/as-patterns, guards, typed holes.
32
+ - **Modules:** file-based projects (`import`, cross-module ADTs/records/externs/measures) + in-file `module`.
33
+ - **Tooling:** full LSP (`pyfun lsp` — diagnostics, hover type/effect, go-to-def, find-refs, rename,
34
+ completion, document/workspace symbols; cross-file; fingerprint-cached, resilient), VS Code client, REPL.
35
+
36
+ **Design mechanics and rationale live in `DESIGN.md` — the source of truth; read it before any
37
+ language/compiler work. The remaining backlog and sequencing live in `ROADMAP.md`. What landed when
38
+ is in git history.** Keep those three current; do not turn this Status section back into a changelog.
39
+
40
+ ## Non-negotiables when working here
41
+
42
+ - **Read `DESIGN.md` first** for any compiler/language change; keep it the source of truth and
43
+ update it when design decisions change.
44
+ - **Do not fork CPython.** Pyfun is a front end targeting Python.
45
+ - **Scope creep is the #1 risk.** The MVP showcase set was *fixed*: effects, **three built-in** CEs
46
+ (`async`/`seq`/`result`), and a **small** units set. Post-MVP, **user-defined CE builders have
47
+ landed** (module-based, desugared — `DESIGN.md` §8.1); still deferred: unit polymorphism beyond what
48
+ shipped, macros, and a package manager (`DESIGN.md` §8, §11). Syntax is cheap; spend effort on
49
+ parser quality, error quality, and predictable lowering.
50
+ - **Lower to a Python-AST IR in Rust, then emit readable Python** — not by string-splicing.
51
+ CEs desugar (`async`→`async`/`await`, `seq`→generators, `result`→`Result` short-circuit) and
52
+ units **erase** to plain Python numerics (`DESIGN.md` §8).
53
+ - **Currying is curried-in-types, n-ary-in-output.** Functions curry by default, but fully-applied
54
+ calls collapse to direct `f(a, b)` — closures only for real partial application; the Python
55
+ boundary stays n-ary (`DESIGN.md` §5, §6). `|>` is pure lowering-time sugar.
56
+ - **Effects, CEs, and units are checked in the type-checking phase** alongside HM inference
57
+ (see `DESIGN.md` §4, §8, §10).
58
+ - **Naming:** `Pyfun` in prose, lowercase `pyfun` for crate/CLI/package, `.pyfun` extension.
59
+
60
+ ## Project layout
61
+
62
+ Dependency-free Rust crate. See `DESIGN.md` §9 for the full module breakdown and build order.
63
+ `src/lexer/` (tokenizer), `src/parser/` (recursive descent; `parser/ast.rs` holds the span-carrying
64
+ AST), `src/ast/` (pretty-printer), `src/types/` (HM inference + ADTs + effects + units), `src/desugar.rs`
65
+ (CE builders, sections, composition), `src/diagnostics/` (rustc-style span rendering), `src/lowering/`
66
+ (Pyfun AST → Python IR), `src/python_emitter/` (Python IR + source emitter), `src/project/` (module
67
+ graph), `src/lsp/` (language server), `src/repl.rs`, `src/main.rs` (CLI), `tests/` (roundtrip +
68
+ typecheck + compile/e2e + lsp + repl), `examples/`. Favor snapshot/golden tests for parser, checker,
69
+ and codegen. AST nodes carry a `NodeSpan` that compares equal unconditionally, so structural
70
+ equality (and the roundtrip tests) ignore spans while diagnostics still have real locations.
71
+
72
+ ## Commands
73
+
74
+ Toolchain is pinned to **Rust 1.96** (`rust-toolchain.toml`); edition 2024, MSRV 1.96. rustup
75
+ auto-selects it inside the repo.
76
+
77
+ ```bash
78
+ cargo build # build
79
+ cargo test # all tests (lexer + roundtrip + compile/e2e)
80
+ cargo test parse_print # single test (substring match on test name)
81
+ cargo clippy --all-targets # lint (currently clean)
82
+ cargo fmt # format Rust sources
83
+ cargo run -- check examples/hello.pyfun # type-check, rustc-style diagnostics
84
+ cargo run -- compile examples/hello.pyfun # type-check then lower to Python (stdout)
85
+ cargo run -- compile foo.pyfun -o foo.py # write Python to a file
86
+ cargo run -- run examples/hello.pyfun # compile then execute via python/python3
87
+ cargo run -- parse examples/hello.pyfun # canonical Pyfun pretty-print
88
+ cargo run -- repl # interactive read-eval-print loop
89
+ ```
90
+
91
+ > **Windows note:** the VS Code LSP client keeps `target/debug/pyfun.exe` locked, so `cargo build`/`run`
92
+ > can fail with "Access is denied (os error 5)". Kill the stray process first
93
+ > (`Get-Process pyfun | Stop-Process -Force`), or build to a separate dir (`CARGO_TARGET_DIR=target-test`).
94
+
95
+ The **REPL** (`src/repl.rs`, a binary-crate module over the `pyfun` lib) keeps session *definitions*
96
+ as accumulated source; each entry is type-checked via `analyze` against them — a `let`/`type`/`extern`
97
+ is remembered and echoes its inferred type, an expression is compiled (accumulated defs + `print (e)`,
98
+ or bare for a `unit` expr) and run once through Python. Commands: `:type`, `:{ … :}` (multi-line, e.g.
99
+ mutually-recursive defs), `:reset`, `:help`, `:quit`. Single-line entries (outside `:{`); defs re-run
100
+ each eval, so pure state feels persistent but top-level effects / `let mut` don't carry across entries.
101
+
102
+ The end-to-end tests in `tests/compile.rs` run the emitted Python through `python`/`python3`; they
103
+ **skip** (not fail) if no interpreter is on PATH. **Pyfun targets Python 3.12+** (emitted code uses
104
+ `match`/`case` and PEP 701 f-strings); the dev/test machine runs **3.14.6**. If `python` on PATH is
105
+ older (e.g. a Windows Store 3.11 shim), the f-string nested-quote e2e test will *fail* rather than
106
+ skip — put a 3.12+ interpreter first on PATH.
@@ -0,0 +1,7 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "pyfun"
7
+ version = "0.0.1"
@@ -0,0 +1,22 @@
1
+ [package]
2
+ name = "pyfun"
3
+ version = "0.0.1"
4
+ edition = "2024"
5
+ rust-version = "1.96"
6
+ description = "An F#-inspired, functional-first language that compiles to readable Python."
7
+ authors = ["Simon Treanor"]
8
+ license = "Apache-2.0"
9
+ repository = "https://github.com/simontreanor/Pyfun"
10
+ readme = "README.md"
11
+
12
+ # Phase 1 is intentionally dependency-free (pure std): lexer + parser + AST + pretty-printer.
13
+ # clap (CLI), an LSP crate, etc. arrive in later phases — see DESIGN.md.
14
+ [dependencies]
15
+
16
+ [[bin]]
17
+ name = "pyfun"
18
+ path = "src/main.rs"
19
+
20
+ [lib]
21
+ name = "pyfun"
22
+ path = "src/lib.rs"