Skip to content

Latest commit

 

History

History
107 lines (83 loc) · 5.12 KB

CONTRIBUTING.md

File metadata and controls

107 lines (83 loc) · 5.12 KB

Contributing to Pyfun

Thank you for taking a look. Pyfun is an F#-inspired, functional-first language whose compiler is written in Rust and emits readable Python. Contributions of every size are welcome: a clearer error message, a missing test, a lesson that explains something better, or a compiler change.

Getting set up

You need two things.

  • Rust. The repository pins its toolchain in rust-toolchain.toml, and rustup selects it automatically inside the repo. The crate has no dependencies, so the first build is quick.
  • Python 3.12 or newer on your PATH as python or python3. The end-to-end tests run the emitted Python, and emitted code uses match statements and PEP 701 f-strings. CI runs them on 3.12 on all three systems and on 3.14 on Linux, so a difference between the two ends of the supported range fails there even when your local run passes.

Then:

cargo build
cargo test                                 # the whole suite
cargo test parse_print                     # one test, by substring
cargo run -- run examples/hello.pyfun      # compile and run a program
cargo run -- compile examples/hello.pyfun  # see the Python it emits

On Windows, an editor running the Pyfun language server keeps target/debug/pyfun.exe open, so a build can fail with "Access is denied". Stop the pyfun process, or build into a second directory with CARGO_TARGET_DIR=target-test.

Finding your way around

Start with the compiler tour, which walks the pipeline from lexing to emission one chapter at a time. Every chapter ends with a "Where you would add..." note naming the first file and function you would touch for a typical change at that stage:

You want to... Read
add a token or keyword Lexing
add an expression form Parsing
add syntax sugar Desugaring
give a builtin its type Inference
add a pattern form Exhaustiveness
add a measure or root Units
lower a stdlib function Lowering
emit a new Python construct Emission
improve an error message Diagnostics
add an editor feature Tooling

Three documents at the root carry the detail. DESIGN.md is the source of truth for what the language means and why; read the relevant section before changing a language rule. INTERNALS.md maps each rule to the code that implements it. ROADMAP.md holds the backlog, including the non-goals, each with the reason it was decided against.

The issues labelled good first issue are small and self-contained, and each one names the file to start from.

Making a change

  1. Open an issue first for anything that changes the language itself, so the design can be agreed before you spend time on code. Bug fixes, tests, diagnostics and docs can go straight to a PR.

  2. Add a test next to the ones like it. tests/roundtrip.rs covers parsing, tests/typecheck.rs the checker and its messages, tests/compile.rs the emitted Python and what it prints, and src/lsp/mod.rs the language server.

  3. Run the same checks CI runs:

    cargo fmt
    cargo clippy --all-targets
    cargo test
  4. Update DESIGN.md or INTERNALS.md in the same PR when your change touches what they describe, and strike through any ROADMAP.md item it closes.

A few principles shape what gets merged:

  • The emitted Python should read as if a person wrote it. Lowering builds a Python syntax tree in Rust and prints it, and a fully applied curried call becomes a plain f(a, b).
  • New syntax mirrors a form Python programmers already know. Ask how Python spells the idea before inventing a spelling.
  • Error messages say what to do next. A message that names the fix is worth more than a new feature.
  • The scope is deliberately small. Proposals for type classes, macros and similar are listed as non-goals in ROADMAP.md, with the reasoning.

Writing lessons and docs

The learning site lives in docs/src and builds with mdBook. Lessons talk to the reader as "you", show an example before stating a rule, and keep to plain prose. Every code block in a lesson is checked against the real compiler, so after editing one run:

cargo build
python docs/verify_lessons.py
python docs/build_notebooks.py

The first checks that each playground link decodes to the starter shown beside it and that each solution prints what the lesson says it prints. The second regenerates the lesson's notebook in docs/src/notebooks/, which CI compares against the lessons.

Licence

Code contributions are made under the Apache License 2.0, the licence of the project. Teaching prose on the learning site is licensed CC BY 4.0.