|
1 | | -// SPDX-License-Identifier: PMPL-1.0-or-later |
2 | | -// SPDX-FileCopyrightText: 2024-2026 Jonathan D.A. Jewell (hyperpolymath) |
| 1 | +// SPDX-License-Identifier: AGPL-3.0-or-later |
| 2 | +// SPDX-FileCopyrightText: 2024-2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk> |
3 | 3 | = RattleScript |
4 | | -:toc: |
5 | | -:toc-placement: preamble |
| 4 | +:toc: preamble |
| 5 | +:icons: font |
6 | 6 |
|
7 | | -Python-syntax AffineScript. |
8 | | -Write code that looks like Python. |
9 | | -Get affine resource guarantees and typed WASM out. |
10 | | - |
11 | | -**Status: alpha** — the syntax, type checker, and WASM output are solid. |
12 | | -Effects runtime is in progress upstream. |
13 | | - |
14 | | ---- |
| 7 | +Python-syntax AffineScript. Write code that looks like Python, get affine resource guarantees and typed-wasm output. |
15 | 8 |
|
16 | 9 | == What it is |
17 | 10 |
|
18 | | -RattleScript is https://github.com/hyperpolymath/affinescript[AffineScript] |
19 | | -with its Python face pre-selected. If you write Python, you already know |
20 | | -most of the syntax. |
| 11 | +RattleScript is link:https://github.com/hyperpolymath/affinescript[AffineScript] with its `rattle` face pre-selected. If you write Python, you already know most of the syntax. The compiler checks that your resources (files, sockets, tokens, handles) are used *exactly as many times as you declare* — and proves it at compile time. No null pointer exceptions. No use-after-free. No silent data races. No GC overhead. |
21 | 12 |
|
22 | | -The compiler checks that your resources (files, sockets, tokens) are used |
23 | | -*exactly as many times as you say* — and proves it at compile time. |
24 | | -No null pointer exceptions. No use-after-free. No silent data races. |
| 13 | +This repo is a **brand surface only**. The compiler, type checker, borrow checker, and codegen all live in link:https://github.com/hyperpolymath/affinescript[affinescript]. This repo carries: |
25 | 14 |
|
26 | | -[source,python] |
27 | | ----- |
28 | | -def greet(name: String) -> String: |
29 | | - "Hello, " + name + "!" |
| 15 | +* Examples idiomatic to Python developers |
| 16 | +* Documentation aimed at the Python community |
| 17 | +* A `rattle` shim CLI that aliases `affinescript --face rattle` |
| 18 | +* Tutorial and migration guides for moving Python code into a strongly-typed, affine-typed, WASM-targeting world |
30 | 19 |
|
31 | | -def main() -> (): |
32 | | - let msg = greet("world") |
33 | | - IO.println(msg) |
34 | | ----- |
| 20 | +== Hello |
35 | 21 |
|
36 | | -Save as `hello.rattle` and run: |
| 22 | +`examples/hello.affine`: |
37 | 23 |
|
38 | | -[source,bash] |
39 | | ----- |
40 | | -rattle eval hello.rattle |
| 24 | +[source,affine] |
41 | 25 | ---- |
| 26 | +# face: rattlescript |
42 | 27 |
|
43 | | -== Why Python syntax? |
44 | | - |
45 | | -Because Python programmers deserve a sound type system. |
46 | | - |
47 | | -RattleScript is Python's more dangerous cousin — same comfortable |
48 | | -whitespace-delimited blocks, but with teeth: the compiler tracks *ownership* |
49 | | -of every value so you cannot accidentally drop, duplicate, or outlive a |
50 | | -resource. You never null-check. You never wonder if a function consumes its |
51 | | -argument or borrows it. The type says so. |
52 | | - |
53 | | -The mascot is a rattlesnake. |
54 | | - |
55 | | -== Surface mappings |
56 | | - |
57 | | -[cols="1,1"] |
58 | | -|=== |
59 | | -|Python surface |What it means in RattleScript |
| 28 | +effect IO: |
| 29 | + fn println(s: String) -> (); |
60 | 30 |
|
61 | | -|`def f(x: T) -> R:` |function declaration |
62 | | -|`True` / `False` |`true` / `false` |
63 | | -|`None` |unit `()` — not null, genuinely nothing |
64 | | -|`and` / `or` / `not` |`&&` / `\|\|` / `!` |
65 | | -|`class Name:` |`type Name` (algebraic data type) |
66 | | -|`pass` |`()` (unit expression) |
67 | | -|`import a.b` |`use a::b;` |
68 | | -|`from a import b` |`use a::b;` |
69 | | -|`if cond:` / `else:` |`if cond { ... } else { ... }` |
70 | | -|`elif cond:` |`} else if cond {` |
71 | | -|`for x in e:` |`for x in e { ... }` |
72 | | -|`match e:` |`match e { ... }` |
73 | | -|`# comment` |`// comment` |
74 | | -|=== |
75 | | - |
76 | | -Run `rattle preview-python-transform <file>` to see the canonical |
77 | | -AffineScript that the preprocessor generates from any `.rattle` file. |
78 | | - |
79 | | -== Getting started |
80 | | - |
81 | | -=== Prerequisites |
| 31 | +def main() -{IO}-> (): |
| 32 | + let greeting = "Hello, RattleScript!" |
| 33 | + println(greeting) |
| 34 | +---- |
82 | 35 |
|
83 | | -* https://ocaml.org/[OCaml] + https://dune.build/[dune] (for the compiler) |
84 | | -* https://www.rust-lang.org/[Rust] + cargo (for the `rattle` wrapper) |
85 | | -* https://just.systems/[just] (task runner) |
| 36 | +Same logical program, written in Python style. Compiles to the same canonical AST and the same typed-wasm output as every other face. |
86 | 37 |
|
87 | | -=== Install from source |
| 38 | +== Install |
88 | 39 |
|
89 | 40 | [source,bash] |
90 | 41 | ---- |
91 | | -git clone --recurse-submodules https://github.com/hyperpolymath/rattlescript |
| 42 | +opam install affinescript |
| 43 | +git clone https://github.com/hyperpolymath/rattlescript |
92 | 44 | cd rattlescript |
93 | | -
|
94 | | -# Build affinescript compiler + rattle wrapper in one step |
95 | | -just bootstrap |
96 | | -
|
97 | | -# Optional: install rattle to ~/.cargo/bin |
98 | | -just install |
99 | 45 | ---- |
100 | 46 |
|
101 | | -=== Usage |
| 47 | +The `affinescript` binary does the work. The `bin/rattle` shim in this repo just defaults the `--face` flag. |
| 48 | + |
| 49 | +== Use |
102 | 50 |
|
103 | 51 | [source,bash] |
104 | 52 | ---- |
105 | | -rattle check hello.rattle # type-check |
106 | | -rattle eval hello.rattle # run |
107 | | -rattle compile hello.rattle # compile to WASM |
108 | | -rattle fmt hello.rattle # format |
109 | | -rattle lint hello.rattle # static analysis |
110 | | -
|
111 | | -# See what the Python preprocessor produces |
112 | | -rattle preview-python-transform hello.rattle |
113 | | ----- |
114 | | - |
115 | | -`.rattle` files are detected automatically — no `--face` flag needed. |
116 | | -You can also use `.pyaff` if you prefer the AffineScript naming. |
| 53 | +# Direct, via affinescript: |
| 54 | +affinescript eval --face rattle examples/hello.affine |
| 55 | +affinescript compile --face rattle examples/hello.affine -o hello.wasm |
117 | 56 |
|
118 | | -== Ownership in one minute |
| 57 | +# Or via the rattle shim (same thing): |
| 58 | +./bin/rattle eval examples/hello.affine |
| 59 | +./bin/rattle compile examples/hello.affine -o hello.wasm |
119 | 60 |
|
120 | | -[source,python] |
121 | | ----- |
122 | | -# @linear means: use this exactly once. |
123 | | -def send_token(@linear token: AuthToken) -> Receipt: |
124 | | - network.submit(token) # consumes token — compiler verifies this |
125 | | -
|
126 | | -# Option instead of None crashes. |
127 | | -def safe_divide(a: Int, b: Int) -> Option[Int]: |
128 | | - if b == 0: |
129 | | - None |
130 | | - else: |
131 | | - Some(a / b) |
132 | | -
|
133 | | -def show(result: Option[Int]) -> (): |
134 | | - match result: |
135 | | - Some(n) => IO.println(Int.to_string(n)) |
136 | | - None => IO.println("no result") |
| 61 | +# Or via the justfile: |
| 62 | +just run examples/hello.affine |
| 63 | +just preview examples/hello.affine # show the canonical lowering |
137 | 64 | ---- |
138 | 65 |
|
139 | | -The compiler rejects any program that drops a `@linear` value unused, |
140 | | -uses it more than once, or tries to treat `None` as a value. |
| 66 | +Source files use the canonical `.affine` extension. The face is selected by the `# face: rattlescript` pragma on the first comment line, or by the `--face rattle` flag. |
| 67 | + |
| 68 | +== Different faces, same cube |
141 | 69 |
|
142 | | -== Relationship to AffineScript |
| 70 | +RattleScript is one of six established faces over the AffineScript core: |
143 | 71 |
|
144 | | -RattleScript *is* AffineScript. Same compiler, same type system, same WASM |
145 | | -output. The difference is `--face python` is the default and the entry |
146 | | -point is `rattle` instead of `affinescript`. |
| 72 | +* AffineScript — the canonical face |
| 73 | +* **RattleScript** — Python-style (this repo) |
| 74 | +* link:https://github.com/hyperpolymath/jaffascript[JaffaScript] — JavaScript / TypeScript-style |
| 75 | +* link:https://github.com/hyperpolymath/lucidscript[LucidScript] — PureScript / Haskell-style |
| 76 | +* link:https://github.com/hyperpolymath/cafescripto[CafeScripto] — CoffeeScript-style |
| 77 | +* link:https://github.com/hyperpolymath/pseudoscript[PseudoScript] — pseudocode-style |
147 | 78 |
|
148 | | -The affinescript compiler lives in the `affinescript/` submodule of this |
149 | | -repo. `just update-affinescript` bumps the pointer to the latest upstream |
150 | | -release — all language improvements flow through automatically. |
| 79 | +All six share the canonical `.affine` extension and lower to the same AST. Errors are reported in face-appropriate vocabulary. |
151 | 80 |
|
152 | | -Face logic: `affinescript/lib/python_face.ml` (syntax transform) and |
153 | | -`affinescript/lib/face.ml` (error vocabulary). No compiler internals change |
154 | | -when the face changes. |
| 81 | +== Why RattleScript |
155 | 82 |
|
156 | | -== Alpha caveats |
| 83 | +Python is the most popular programming language and has the largest scientific / data / web community on Earth. It also has well-documented limits: dynamic typing, packaging hell, the GIL, performance ceiling for browser deployment, no ownership semantics. RattleScript is a Python-shaped on-ramp to a language that fixes those limits without making you learn an alien syntax first. |
157 | 84 |
|
158 | | -* **Effects runtime**: `effect`/`handle` syntax is type-checked but not yet |
159 | | - executed at runtime. The effects story is coming in a near-upstream release. |
160 | | -* **Closures and linear captures**: lambda-body linear capture tracking is |
161 | | - conservative in the current release. |
162 | | -* **Span fidelity**: error locations refer to the transformed canonical source, |
163 | | - not the original `.rattle` line numbers. Source-map remapping is planned. |
| 85 | +For a Python developer, the migration path looks like: |
164 | 86 |
|
165 | | -== Contributing |
| 87 | +1. Rename your `.py` files to `.affine` and add `# face: rattlescript` at the top. |
| 88 | +2. Type-annotate your function signatures (Python's optional type hints become required, but you can usually copy them over). |
| 89 | +3. Add `let` to bare variable assignments. |
| 90 | +4. Replace `import a.b` with explicit imports. |
| 91 | +5. Compile to typed-wasm, ship to browsers / WASI runtimes / native via the same build. |
166 | 92 |
|
167 | | -This repo is a thin distribution layer. Language bugs and features go |
168 | | -upstream: https://github.com/hyperpolymath/affinescript[hyperpolymath/affinescript]. |
| 93 | +== Status |
169 | 94 |
|
170 | | -RattleScript-specific contributions (branding, examples, tutorials, the |
171 | | -`rattle` CLI wrapper) are welcome here. |
| 95 | +Alpha. The face transformer is implemented in `affinescript/lib/python_face.ml`. Known limitations are tracked in link:https://github.com/hyperpolymath/affinescript/blob/main/examples/faces/README.adoc[the affinescript faces README] under "Known transformer gaps". |
172 | 96 |
|
173 | 97 | == License |
174 | 98 |
|
175 | | -PMPL-1.0-or-later. See link:LICENSE[LICENSE]. |
| 99 | +AGPL-3.0-or-later. See `LICENSE`. |
0 commit comments