// SPDX-License-Identifier: CC-BY-SA-4.0
// SPDX-FileCopyrightText: 2024-2026 Jonathan D.A. Jewell (hyperpolymath)
= RattleScript
:toc:
:toc-placement: preamble

Python-syntax AffineScript.
Write code that looks like Python.
Get affine resource guarantees and typed WASM out.

**Status: alpha** — the syntax, type checker, and WASM output are solid.
Effects runtime is in progress upstream.

---

== What it is

RattleScript is https://github.com/hyperpolymath/affinescript[AffineScript]
with its Python face pre-selected.  If you write Python, you already know
most of the syntax.

The compiler checks that your resources (files, sockets, tokens) are used
*exactly as many times as you say* — and proves it at compile time.
No null pointer exceptions.  No use-after-free.  No silent data races.

[source,python]
----
def greet(name: String) -> String:
    "Hello, " + name + "!"

def main() -> ():
    let msg = greet("world")
    IO.println(msg)
----

Save as `hello.rattle` and run:

[source,bash]
----
rattle eval hello.rattle
----

== Why Python syntax?

Because Python programmers deserve a sound type system.

RattleScript is Python's more dangerous cousin — same comfortable
whitespace-delimited blocks, but with teeth: the compiler tracks *ownership*
of every value so you cannot accidentally drop, duplicate, or outlive a
resource.  You never null-check.  You never wonder if a function consumes its
argument or borrows it.  The type says so.

The mascot is a rattlesnake.

== Surface mappings

[cols="1,1"]
|===
|Python surface |What it means in RattleScript

|`def f(x: T) -> R:` |function declaration
|`True` / `False`    |`true` / `false`
|`None`              |unit `()` — not null, genuinely nothing
|`and` / `or` / `not` |`&&` / `\|\|` / `!`
|`class Name:`       |`type Name` (algebraic data type)
|`pass`              |`()` (unit expression)
|`import a.b`        |`use a::b;`
|`from a import b`   |`use a::b;`
|`if cond:` / `else:` |`if cond { ... } else { ... }`
|`elif cond:`        |`} else if cond {`
|`for x in e:`       |`for x in e { ... }`
|`match e:`          |`match e { ... }`
|`# comment`         |`// comment`
|===

Run `rattle preview-python-transform <file>` to see the canonical
AffineScript that the preprocessor generates from any `.rattle` file.

== Getting started

=== Prerequisites

* https://ocaml.org/[OCaml] + https://dune.build/[dune] (for the compiler)
* https://www.rust-lang.org/[Rust] + cargo (for the `rattle` wrapper)
* https://just.systems/[just] (task runner)

=== Install from source

[source,bash]
----
git clone --recurse-submodules https://github.com/hyperpolymath/rattlescript
cd rattlescript

# Build affinescript compiler + rattle wrapper in one step
just bootstrap

# Optional: install rattle to ~/.cargo/bin
just install
----

=== Usage

[source,bash]
----
rattle check   hello.rattle      # type-check
rattle eval    hello.rattle      # run
rattle compile hello.rattle      # compile to WASM
rattle fmt     hello.rattle      # format
rattle lint    hello.rattle      # static analysis

# See what the Python preprocessor produces
rattle preview-python-transform hello.rattle
----

`.rattle` files are detected automatically — no `--face` flag needed.
You can also use `.pyaff` if you prefer the AffineScript naming.

== Ownership in one minute

[source,python]
----
# @linear means: use this exactly once.
def send_token(@linear token: AuthToken) -> Receipt:
    network.submit(token)   # consumes token — compiler verifies this

# Option instead of None crashes.
def safe_divide(a: Int, b: Int) -> Option[Int]:
    if b == 0:
        None
    else:
        Some(a / b)

def show(result: Option[Int]) -> ():
    match result:
        Some(n) => IO.println(Int.to_string(n))
        None    => IO.println("no result")
----

The compiler rejects any program that drops a `@linear` value unused,
uses it more than once, or tries to treat `None` as a value.

== Relationship to AffineScript

RattleScript *is* AffineScript.  Same compiler, same type system, same WASM
output.  The difference is `--face python` is the default and the entry
point is `rattle` instead of `affinescript`.

The affinescript compiler lives in the `affinescript/` submodule of this
repo.  `just update-affinescript` bumps the pointer to the latest upstream
release — all language improvements flow through automatically.

Face logic: `affinescript/lib/python_face.ml` (syntax transform) and
`affinescript/lib/face.ml` (error vocabulary).  No compiler internals change
when the face changes.

== Alpha caveats

* **Effects runtime**: `effect`/`handle` syntax is type-checked but not yet
  executed at runtime.  The effects story is coming in a near-upstream release.
* **Closures and linear captures**: lambda-body linear capture tracking is
  conservative in the current release.
* **Span fidelity**: error locations refer to the transformed canonical source,
  not the original `.rattle` line numbers.  Source-map remapping is planned.

== Contributing

This repo is a thin distribution layer.  Language bugs and features go
upstream: https://github.com/hyperpolymath/affinescript[hyperpolymath/affinescript].

RattleScript-specific contributions (branding, examples, tutorials, the
`rattle` CLI wrapper) are welcome here.

== License

PMPL-1.0-or-later.  See link:LICENSE[LICENSE].
