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.
- pyfun_lang-0.0.1/.github/workflows/wheels.yml +122 -0
- pyfun_lang-0.0.1/.gitignore +20 -0
- pyfun_lang-0.0.1/CLAUDE.md +106 -0
- pyfun_lang-0.0.1/Cargo.lock +7 -0
- pyfun_lang-0.0.1/Cargo.toml +22 -0
- pyfun_lang-0.0.1/DESIGN.md +1640 -0
- pyfun_lang-0.0.1/LICENSE +201 -0
- pyfun_lang-0.0.1/NOTICE +15 -0
- pyfun_lang-0.0.1/PKG-INFO +426 -0
- pyfun_lang-0.0.1/README.md +406 -0
- pyfun_lang-0.0.1/ROADMAP.md +91 -0
- pyfun_lang-0.0.1/build-lsp.ps1 +43 -0
- pyfun_lang-0.0.1/editors/vscode/.gitignore +3 -0
- pyfun_lang-0.0.1/editors/vscode/README.md +142 -0
- pyfun_lang-0.0.1/editors/vscode/extension.js +46 -0
- pyfun_lang-0.0.1/editors/vscode/language-configuration.json +22 -0
- pyfun_lang-0.0.1/editors/vscode/package.json +174 -0
- pyfun_lang-0.0.1/editors/vscode/pyfun.tmLanguage.json +147 -0
- pyfun_lang-0.0.1/examples/hello.pyfun +622 -0
- pyfun_lang-0.0.1/examples/modules/geometry.pyfun +7 -0
- pyfun_lang-0.0.1/examples/modules/main.pyfun +32 -0
- pyfun_lang-0.0.1/examples/modules/store.pyfun +9 -0
- pyfun_lang-0.0.1/pyproject.toml +27 -0
- pyfun_lang-0.0.1/rust-toolchain.toml +5 -0
- pyfun_lang-0.0.1/src/ast/mod.rs +583 -0
- pyfun_lang-0.0.1/src/desugar.rs +277 -0
- pyfun_lang-0.0.1/src/diagnostics/mod.rs +79 -0
- pyfun_lang-0.0.1/src/lexer/mod.rs +1538 -0
- pyfun_lang-0.0.1/src/lexer/token.rs +171 -0
- pyfun_lang-0.0.1/src/lib.rs +235 -0
- pyfun_lang-0.0.1/src/lowering/mod.rs +3115 -0
- pyfun_lang-0.0.1/src/lsp/json.rs +376 -0
- pyfun_lang-0.0.1/src/lsp/mod.rs +2723 -0
- pyfun_lang-0.0.1/src/lsp/resolve.rs +853 -0
- pyfun_lang-0.0.1/src/main.rs +457 -0
- pyfun_lang-0.0.1/src/parser/ast.rs +690 -0
- pyfun_lang-0.0.1/src/parser/mod.rs +1980 -0
- pyfun_lang-0.0.1/src/project/mod.rs +750 -0
- pyfun_lang-0.0.1/src/python_emitter/mod.rs +692 -0
- pyfun_lang-0.0.1/src/repl.rs +312 -0
- pyfun_lang-0.0.1/src/types/mod.rs +6149 -0
- pyfun_lang-0.0.1/tests/cli.rs +230 -0
- pyfun_lang-0.0.1/tests/compile.rs +2576 -0
- pyfun_lang-0.0.1/tests/lsp.rs +634 -0
- pyfun_lang-0.0.1/tests/project.rs +778 -0
- pyfun_lang-0.0.1/tests/repl.rs +98 -0
- pyfun_lang-0.0.1/tests/roundtrip.rs +680 -0
- pyfun_lang-0.0.1/tests/run.rs +92 -0
- 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,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"
|