Skip to content

Commit 24980a0

Browse files
hyperpolymathclaude
andcommitted
fix(standalone): correct paths for submodule context + polish README
- build.rs: check affinescript/_build/ (standalone submodule) before ../../_build/ (monorepo dev path). Both paths tried; env var and PATH still take priority. - justfile: add setup/bootstrap/update-affinescript recipes for standalone workflow; remove monorepo-relative paths. - README.adoc: rewritten for standalone perspective — correct install instructions, honest alpha caveats (effects runtime, span fidelity), ownership-in-one-minute example, contributing note pointing upstream. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 96af312 commit 24980a0

3 files changed

Lines changed: 149 additions & 97 deletions

File tree

README.adoc

Lines changed: 105 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,22 @@
66

77
Python-syntax AffineScript.
88
Write code that looks like Python.
9-
Get affine resource guarantees, algebraic effects, and typed WASM out.
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.
1013

1114
---
1215

1316
== What it is
1417

15-
RattleScript is AffineScript with its Python face pre-selected.
16-
If you write Python, you already know most of the syntax.
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.
21+
1722
The compiler checks that your resources (files, sockets, tokens) are used
1823
*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.
1925

2026
[source,python]
2127
----
@@ -27,7 +33,7 @@ def main() -> ():
2733
IO.println(msg)
2834
----
2935

30-
That's a valid `.rattle` file. Run it:
36+
Save as `hello.rattle` and run:
3137

3238
[source,bash]
3339
----
@@ -37,106 +43,133 @@ rattle eval hello.rattle
3743
== Why Python syntax?
3844

3945
Because Python programmers deserve a sound type system.
40-
RattleScript is Python's more dangerous cousin — same comfortable whitespace-
41-
delimited blocks, but with teeth: no null pointer exceptions, no
42-
use-after-free, no data races, and no runtime surprises that the type
43-
checker already knows about.
4446

45-
The mascot is a rattlesnake. Python's more dangerous cousin.
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.
4654

4755
== Surface mappings
4856

4957
[cols="1,1"]
5058
|===
51-
|Python surface |AffineScript equivalent
52-
53-
|`def f(x: T) -> R:` |`fn f(x: T) -> R { ... }`
54-
|`True` / `False` |`true` / `false`
55-
|`None` |`()`
56-
|`and` / `or` / `not` |`&&` / `\|\|` / `!`
57-
|`class Name:` |`type Name`
58-
|`pass` |`()`
59-
|`import a.b` |`use a::b;`
60-
|`from a import b` |`use a::b;`
61-
|`if cond:` / `else:` |`if cond { ... } else { ... }`
62-
|`for x in e:` |`for x in e { ... }`
63-
|`match e:` |`match e { ... }`
64-
|`# comment` |`// comment`
59+
|Python surface |What it means in RattleScript
60+
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`
6574
|===
6675

67-
See `rattle preview-python-transform <file>` to inspect the full transform
68-
for any file.
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
6982

70-
== Installation
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)
7186

72-
=== From source (monorepo dev)
87+
=== Install from source
7388

7489
[source,bash]
7590
----
76-
# From nextgen-languages/affinescript root — build affinescript first:
77-
dune build
91+
git clone --recurse-submodules https://github.com/hyperpolymath/rattlescript
92+
cd rattlescript
7893
79-
# Then build the rattle wrapper:
80-
cd distributions/rattlescript
81-
just build
82-
# Binary at: target/debug/rattle
94+
# Build affinescript compiler + rattle wrapper in one step
95+
just bootstrap
96+
97+
# Optional: install rattle to ~/.cargo/bin
98+
just install
8399
----
84100

85-
=== Installed release
101+
=== Usage
86102

87103
[source,bash]
88104
----
89-
just install
90-
# Installs rattle to ~/.cargo/bin
91-
----
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
92110
93-
=== Standalone GitHub repo
111+
# See what the Python preprocessor produces
112+
rattle preview-python-transform hello.rattle
113+
----
94114

95-
The standalone https://github.com/hyperpolymath/rattlescript[hyperpolymath/rattlescript]
96-
repo contains a pre-configured distribution with affinescript as a git
97-
submodule. Use that for a self-contained checkout.
115+
`.rattle` files are detected automatically — no `--face` flag needed.
116+
You can also use `.pyaff` if you prefer the AffineScript naming.
98117

99-
== Usage
118+
== Ownership in one minute
100119

101-
[source,bash]
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")
102137
----
103-
# Type-check a file
104-
rattle check hello.rattle
105138

106-
# Evaluate
107-
rattle eval hello.rattle
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.
108141

109-
# Compile to WASM
110-
rattle compile hello.rattle
142+
== Relationship to AffineScript
111143

112-
# See the canonical AffineScript generated by the preprocessor
113-
rattle preview-python-transform hello.rattle
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`.
114147

115-
# All affinescript subcommands work — --face python is pre-injected
116-
rattle lint hello.rattle
117-
rattle fmt hello.rattle
118-
----
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.
119151

120-
== Relationship to AffineScript
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.
155+
156+
== Alpha caveats
121157

122-
RattleScript *is* AffineScript — same compiler, same type system, same WASM
123-
output. The only difference is `--face python` is the default.
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.
124164

125-
The Python face lives in
126-
link:../../lib/python_face.ml[`lib/python_face.ml`] and the error vocabulary
127-
in link:../../lib/face.ml[`lib/face.ml`]. No compiler internals change when
128-
the face changes.
165+
== Contributing
129166

130-
== Sync strategy
167+
This repo is a thin distribution layer. Language bugs and features go
168+
upstream: https://github.com/hyperpolymath/affinescript[hyperpolymath/affinescript].
131169

132-
This directory (`distributions/rattlescript/`) lives inside the affinescript
133-
monorepo. The standalone https://github.com/hyperpolymath/rattlescript[rattlescript]
134-
GitHub repo uses affinescript as a git submodule and contains only:
170+
RattleScript-specific contributions (branding, examples, tutorials, the
171+
`rattle` CLI wrapper) are welcome here.
135172

136-
* This `distributions/rattlescript/` content (copied at release time)
137-
* Branding assets
138-
* CI workflows
173+
== License
139174

140-
`just update-affinescript` advances the submodule pointer after each
141-
affinescript release. All language changes, face changes, and bug fixes
142-
come through that single submodule bump — no duplication.
175+
PMPL-1.0-or-later. See link:LICENSE[LICENSE].

build.rs

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,35 +6,47 @@
66
//! Checks, in order:
77
//! 1. `AFFINESCRIPT` env var (explicit override)
88
//! 2. `affinescript` on PATH (standard install)
9-
//! 3. Sibling build output `../../_build/default/bin/main.exe` (monorepo dev)
9+
//! 3. `affinescript/_build/default/bin/main.exe` (standalone repo, submodule built)
10+
//! 4. `../../_build/default/bin/main.exe` (monorepo dev, distributions/rattlescript/)
1011
//!
1112
//! The resolved path is baked in as the `AFFINESCRIPT_BIN` compile-time env.
13+
//! Run `just setup` to build affinescript from the submodule before `cargo build`.
1214
1315
use std::env;
1416
use std::path::Path;
1517

1618
fn main() {
1719
println!("cargo:rerun-if-env-changed=AFFINESCRIPT");
1820

21+
let manifest = env::var("CARGO_MANIFEST_DIR").unwrap();
22+
1923
let bin = if let Ok(p) = env::var("AFFINESCRIPT") {
2024
p
2125
} else if which_affinescript() {
2226
"affinescript".to_string()
2327
} else {
24-
// Monorepo dev path: distributions/rattlescript/ → ../../_build/...
25-
let manifest = env::var("CARGO_MANIFEST_DIR").unwrap();
26-
let dev_path = Path::new(&manifest)
27-
.join("../../_build/default/bin/main.exe")
28+
// Standalone repo: submodule built with `just setup`
29+
let standalone_path = Path::new(&manifest)
30+
.join("affinescript/_build/default/bin/main.exe")
2831
.canonicalize()
2932
.ok()
3033
.map(|p| p.to_string_lossy().into_owned());
3134

32-
match dev_path {
33-
Some(p) => p,
34-
None => {
35+
if let Some(p) = standalone_path {
36+
p
37+
} else {
38+
// Monorepo dev: distributions/rattlescript/ → ../../_build/...
39+
let monorepo_path = Path::new(&manifest)
40+
.join("../../_build/default/bin/main.exe")
41+
.canonicalize()
42+
.ok()
43+
.map(|p| p.to_string_lossy().into_owned());
44+
45+
monorepo_path.unwrap_or_else(|| {
3546
// Fall back to the name — will fail at runtime with a clear message.
47+
// Run `just setup` to build affinescript first.
3648
"affinescript".to_string()
37-
}
49+
})
3850
}
3951
};
4052

justfile

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,53 @@
11
# SPDX-License-Identifier: PMPL-1.0-or-later
22
# SPDX-FileCopyrightText: 2024-2026 Jonathan D.A. Jewell (hyperpolymath)
33

4-
# RattleScript distribution justfile.
5-
# Most work lives in the parent affinescript justfile.
6-
# This file handles the thin Rust wrapper binary only.
4+
# RattleScript justfile.
5+
# First-time setup: `just setup` builds affinescript from the submodule,
6+
# then `just build` builds the rattle wrapper binary.
77

88
default:
99
just --list
1010

11-
# Build the rattle binary (dev mode, uses sibling _build/)
11+
# First-time setup: init submodule and build affinescript
12+
setup:
13+
git submodule update --init --recursive
14+
cd affinescript && dune build
15+
16+
# Update affinescript submodule to latest and rebuild
17+
update-affinescript:
18+
git submodule update --remote affinescript
19+
cd affinescript && dune build
20+
git add affinescript
21+
git commit -m "chore(affinescript): advance pointer to latest release"
22+
23+
# Build the rattle binary (dev mode)
1224
build:
1325
cargo build
1426

1527
# Build optimised release binary
1628
release:
1729
cargo build --release
1830

19-
# Run the example
31+
# First-time: setup then build
32+
bootstrap: setup build
33+
34+
# Run the hello example
2035
run-hello:
2136
cargo run -- eval examples/hello.rattle
2237

23-
# Check example typechecks
38+
# Type-check the hello example
2439
check-hello:
2540
cargo run -- check examples/hello.rattle
2641

2742
# Show the Python-face transform for the hello example
2843
preview-hello:
2944
cargo run -- preview-python-transform examples/hello.rattle
3045

31-
# Run all examples
46+
# Type-check all examples
3247
check-examples:
3348
cargo run -- check examples/hello.rattle
3449
cargo run -- check examples/ownership.rattle
3550

3651
# Install rattle binary to ~/.cargo/bin
37-
install:
52+
install: setup
3853
cargo install --path .
39-
40-
# Bump AffineScript submodule pointer (called by CI after affinescript releases)
41-
update-affinescript:
42-
#!/usr/bin/env bash
43-
set -euo pipefail
44-
git -C ../.. submodule update --remote
45-
git add ../..
46-
git commit -m "chore(affinescript): advance pointer to latest release"

0 commit comments

Comments
 (0)