Skip to content

Commit c3ef2ee

Browse files
nanofuxionclaude
andcommitted
Write the examples in SxfeScript, not JavaScript
A site for a language showed four of its six programs in plain JavaScript, and the quick start opened on a .js file. The language is the point, so the examples lead with it now. server, fetch, files and ffi move from .mjs to .sx, with the annotations that earn their place. Nothing needed an .mjs extension for its own sake -- .sx is always a module, so top-level await works without one. Every one of them was run before it was written down, and CMake now runs the four offline ones as tests: the docs inline these files verbatim, so an example that stops working is a broken code block published to the web. The quick start is reordered to match: .sx first, then ownership, then a server, and JavaScript and TypeScript at the end where they belong -- as the compatibility story rather than the headline. The two places that stay JavaScript are the ones demonstrating exactly that: the plain-JS section here, and LANGUAGE.md's example of what the compatibility transformer lowers `unsafe extern` to. GitHub's renderer has no .sx highlighter and returns an ```sx fence as flat text, so build-docs.py relabels the fence as TypeScript on the way in and re-tags mut/safe/unsafe as keywords on the way out. The markdown keeps saying sx. The landing page now names the file it is quoting instead of carrying a copy, the way the examples page already did, and its hero snippets go through the same renderer. Both were hand-maintained HTML that could drift from the programs they claimed to show, and neither was highlighted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 2da9eb3 commit c3ef2ee

16 files changed

Lines changed: 238 additions & 189 deletions

CMakeLists.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,14 @@ if(BUILD_TESTING AND SXN_BUILD_TESTS)
168168
add_test(NAME sxfe-frontend COMMAND sxfe-tests)
169169
add_test(NAME sxn-help COMMAND sxn --help)
170170
add_test(NAME sxn-example COMMAND sxn ${CMAKE_CURRENT_SOURCE_DIR}/examples/velocity.sx)
171+
# The docs site inlines every one of these programs verbatim, so an example
172+
# that stops running is a broken code block published to the web. fetch.sx
173+
# is left out on purpose: it is the only one that needs the network.
174+
foreach(sxn_example hello server files ffi)
175+
add_test(NAME sxn-example-${sxn_example}
176+
COMMAND sxn ${CMAKE_CURRENT_SOURCE_DIR}/examples/${sxn_example}.sx)
177+
set_tests_properties(sxn-example-${sxn_example} PROPERTIES TIMEOUT 30)
178+
endforeach()
171179
add_test(NAME sxn-memory-report COMMAND sxn --memory-report ${CMAKE_CURRENT_SOURCE_DIR}/examples/velocity.sx)
172180
add_test(NAME sxn-memory-usage COMMAND sxn ${CMAKE_CURRENT_SOURCE_DIR}/tests/fixtures/memory-usage.sx)
173181
add_test(NAME sxn-native-safe COMMAND sxn ${CMAKE_CURRENT_SOURCE_DIR}/benchmarks/compute-native-safe.sx)

README.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,17 @@ surfaces, native calling, bytecode, and the full performance write-up behind
3434
the two tables below -- lives in [`spec/`](spec/); see that directory's own
3535
files for each topic.
3636

37-
Complete programs that run as-is are in [`examples/`](examples/):
37+
Complete programs that run as-is are in [`examples/`](examples/), all of them
38+
`.sx`:
3839

3940
| File | What it shows |
4041
|---|---|
4142
| [`hello.sx`](examples/hello.sx) | Erasable types, `let mut`, and an `&mut` borrow |
4243
| [`velocity.sx`](examples/velocity.sx) | A primitive-only interface as a fixed-layout struct |
43-
| [`server.mjs`](examples/server.mjs) | `Sxn.serve` with `Request`/`Response` routing and a JSON body |
44-
| [`fetch.mjs`](examples/fetch.mjs) | `fetch`, then the same response read as a stream |
45-
| [`files.mjs`](examples/files.mjs) | `Sxn.file`/`Sxn.write`, and `node:fs` over the same file |
46-
| [`ffi.mjs`](examples/ffi.mjs) | Calling a C function through `Sxn.ffi` |
44+
| [`server.sx`](examples/server.sx) | `Sxn.serve` with `Request`/`Response` routing and a JSON body |
45+
| [`fetch.sx`](examples/fetch.sx) | `fetch`, then the same response read as a stream |
46+
| [`files.sx`](examples/files.sx) | `Sxn.file`/`Sxn.write`, and `node:fs` over the same file |
47+
| [`ffi.sx`](examples/ffi.sx) | Calling a C function through `Sxn.ffi` |
4748

4849
## Install
4950

@@ -121,8 +122,8 @@ already loads native code in its engine core on every platform including
121122
mobile, and has no Node layer to put an addon loader in.)
122123

123124
A third document, **`spec/BYTECODE.md`**, covers `.sxbc`: `sxn compile
124-
app.js` produces bytecode for distribution (`--strip` drops the compiling
125-
machine's own paths from it), `sxn --compile-cache app.js` compiles once and
125+
app.sx` produces bytecode for distribution (`--strip` drops the compiling
126+
machine's own paths from it), `sxn --compile-cache app.sx` compiles once and
126127
reuses the result on later launches, and `sxn app.sxbc` runs either one
127128
directly. Real, measured gains -- see that document for the numbers -- and
128129
proportional to how much there is to parse: noticeable on a large file,

docs/_page.html

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -97,16 +97,6 @@
9797
}
9898
.doc h2:hover .anchor, .doc h3:hover .anchor, .doc .anchor:focus { opacity: 1; }
9999

100-
/* The renderer emits GitHub's own token classes. Map the handful that
101-
actually appear onto this site's palette rather than shipping GitHub's
102-
stylesheet for them. */
103-
.pl-c { color: var(--ink-soft); font-style: italic; }
104-
.pl-k { color: var(--unsafe); }
105-
.pl-s, .pl-pds, .pl-sr { color: var(--safe); }
106-
.pl-c1, .pl-cce { color: var(--accent); }
107-
.pl-en, .pl-e, .pl-ent { color: var(--ink); font-weight: 600; }
108-
.pl-s1, .pl-smi, .pl-v { color: var(--ink-mid); }
109-
110100
/* ---- prev/next ---- */
111101
.doc-nav { display: flex; justify-content: space-between; gap: 14px; margin-top: 46px; flex-wrap: wrap; }
112102
.doc-nav a {

docs/guide/examples.md

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,16 @@ inlined from those files when this page is built, so it cannot drift out of
77
step with them. Clone the repo and run them, or paste one into a file and run
88
that.
99

10+
They are all `.sx`, because that is the language this project is for.
11+
Everything a `.sx` file can do here, a plain `.js`, `.mjs` or `.ts` file can
12+
do too — the annotations and the ownership syntax are the only difference, and
13+
[the quick start](../quickstart/) shows the same program in each.
14+
1015
## Types and borrows
1116

1217
[`examples/hello.sx`](https://github.com/SxfeScript/sxfescript/blob/main/examples/hello.sx)
1318

14-
<!-- include: examples/hello.sx as ts -->
19+
<!-- include: examples/hello.sx as sx -->
1520

1621
```sh
1722
sxn examples/hello.sx
@@ -31,7 +36,7 @@ describes a struct with declared field order and natural alignment — the same
3136
layout on every supported target, which is what code crossing into native
3237
memory needs.
3338

34-
<!-- include: examples/velocity.sx as ts -->
39+
<!-- include: examples/velocity.sx as sx -->
3540

3641
```sh
3742
sxn examples/velocity.sx
@@ -43,16 +48,16 @@ sxn examples/velocity.sx
4348

4449
## An HTTP server
4550

46-
[`examples/server.mjs`](https://github.com/SxfeScript/sxfescript/blob/main/examples/server.mjs)
51+
[`examples/server.sx`](https://github.com/SxfeScript/sxfescript/blob/main/examples/server.sx)
4752

4853
The handler receives a `Request` and returns a `Response`. `req.url` is
4954
absolute, so `new URL(req.url)` gives you the path and query, and
5055
`await req.json()` reads the body.
5156

52-
<!-- include: examples/server.mjs as js -->
57+
<!-- include: examples/server.sx as sx -->
5358

5459
```sh
55-
sxn examples/server.mjs
60+
sxn examples/server.sx
5661
```
5762

5863
```
@@ -66,15 +71,15 @@ pick a free one. Pass a real port number to choose it yourself.
6671

6772
## fetch and streams
6873

69-
[`examples/fetch.mjs`](https://github.com/SxfeScript/sxfescript/blob/main/examples/fetch.mjs)
74+
[`examples/fetch.sx`](https://github.com/SxfeScript/sxfescript/blob/main/examples/fetch.sx)
7075

7176
A response body is a real `ReadableStream`, so it can be piped and consumed a
7277
chunk at a time rather than only read whole.
7378

74-
<!-- include: examples/fetch.mjs as js -->
79+
<!-- include: examples/fetch.sx as sx -->
7580

7681
```sh
77-
sxn examples/fetch.mjs
82+
sxn examples/fetch.sx
7883
```
7984

8085
```
@@ -85,15 +90,16 @@ sxn examples/fetch.mjs
8590

8691
## Files
8792

88-
[`examples/files.mjs`](https://github.com/SxfeScript/sxfescript/blob/main/examples/files.mjs)
93+
[`examples/files.sx`](https://github.com/SxfeScript/sxfescript/blob/main/examples/files.sx)
8994

90-
`Sxn.file` and `Sxn.write` are the runtime's own file I/O. `node:fs` works
91-
too, over the same files, for code that already expects it.
95+
`Sxn.file` and `Sxn.write` are the runtime's own file I/O. The `node:fs`
96+
import is the compatibility layer reading the same file back, for code that
97+
already expects Node.
9298

93-
<!-- include: examples/files.mjs as js -->
99+
<!-- include: examples/files.sx as sx -->
94100

95101
```sh
96-
sxn examples/files.mjs
102+
sxn examples/files.sx
97103
```
98104

99105
```
@@ -104,15 +110,15 @@ sxn version: 0.0.1
104110

105111
## Calling a C function
106112

107-
[`examples/ffi.mjs`](https://github.com/SxfeScript/sxfescript/blob/main/examples/ffi.mjs)
113+
[`examples/ffi.sx`](https://github.com/SxfeScript/sxfescript/blob/main/examples/ffi.sx)
108114

109115
`Sxn.ffi(library, symbol, argumentTypes, returnType)` returns a callable
110-
JavaScript function, through libffi and `dlopen`.
116+
function, through libffi and `dlopen`.
111117

112-
<!-- include: examples/ffi.mjs as js -->
118+
<!-- include: examples/ffi.sx as sx -->
113119

114120
```sh
115-
sxn examples/ffi.mjs
121+
sxn examples/ffi.sx
116122
```
117123

118124
```

docs/guide/quickstart.md

Lines changed: 67 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# Quick start
22

3-
`sxn` is a single binary. It runs `.sx`, `.ts`, `.js`, `.mjs` and `.cjs`
4-
files directly, with no build step and nothing to configure first.
3+
`sxn` is a single binary. It runs `.sx` — this project's own language — plus
4+
`.ts`, `.js`, `.mjs` and `.cjs`, all directly, with no build step and nothing
5+
to configure first.
56

67
## Install
78

@@ -34,68 +35,45 @@ rather unpack one yourself.
3435

3536
## Your first program
3637

37-
Ordinary JavaScript runs as-is. Put this in `hello.js`:
38+
Put this in `hello.sx`:
3839

39-
```js
40-
const runtime = typeof Sxn !== "undefined" ? "sxn " + Sxn.version : "something else";
41-
console.log(`hello from ${runtime}`);
42-
```
43-
44-
```sh
45-
sxn hello.js
46-
```
47-
48-
```
49-
hello from sxn 0.0.1
50-
```
51-
52-
## TypeScript, with no build step
53-
54-
Rename it to `hello.ts` and add annotations. `sxn` parses and strips them
55-
itself, so there is no `tsc` and no bundler in front of it:
56-
57-
```ts
58-
interface Runtime {
40+
```sx
41+
interface Repo {
5942
name: string;
60-
version: string;
43+
stars: i32;
6144
}
6245
63-
function describe(r: Runtime): string {
64-
return `hello from ${r.name} ${r.version}`;
65-
}
46+
const describe = (repo: Repo): string =>
47+
`${repo.name} has ${repo.stars} star${repo.stars === 1 ? "" : "s"}`;
6648
67-
console.log(describe({ name: "sxn", version: Sxn.version }));
49+
console.log(describe({ name: "sxfescript", stars: 1 }));
6850
```
6951

7052
```sh
71-
sxn hello.ts
53+
sxn hello.sx
7254
```
7355

7456
```
75-
hello from sxn 0.0.1
57+
sxfescript has 1 star
7658
```
7759

78-
Types that can be erased are accepted: aliases, interfaces, `declare`,
79-
annotations, optional parameters, generics on functions, `as`/`satisfies`,
80-
and union types. `enum` and `namespace` are rejected on purpose rather than
81-
stripped, because both emit a real object at runtime in TypeScript, and
82-
quietly removing them would turn every use of their members into `undefined`:
83-
84-
```
85-
SyntaxError: unsupported keyword: enum
86-
```
60+
That is an ordinary interface and an ordinary annotation, and there is no
61+
`tsc` and no bundler in front of it. `sxn` parses the types itself and strips
62+
them as it goes.
8763

88-
## `.sx`: ownership and borrows
64+
## Ownership and borrows
8965

90-
A `.sx` file is the same language with mutation and aliasing made explicit.
91-
`let mut` is a mutable owner, `let` an immutable one, `&` borrows a value
92-
shared, and `&mut` borrows it exclusively:
66+
`.sx` is the same language with mutation and aliasing made explicit. `let mut`
67+
is a mutable owner, `let` an immutable one, `&` borrows a value shared, and
68+
`&mut` borrows it exclusively:
9369

94-
```ts
70+
```sx
9571
interface Counter {
9672
hits: i32;
9773
}
9874
75+
// &mut borrows the counter exclusively, so bump can change what it was
76+
// handed without taking ownership of it.
9977
function bump(c: &mut Counter): void {
10078
c.hits += 1;
10179
}
@@ -114,20 +92,24 @@ sxn counter.sx
11492
counter: 2
11593
```
11694

117-
The syntax is parsed natively today. The full control-flow ownership pass
118-
that enforces every rule in
119-
[the language contract](../language/) is still being written —
120-
[the implementation ledger](../implementation/) tracks exactly what is
121-
checked and what is only parsed, and it is worth reading before you rely on a
122-
rule being enforced.
95+
An interface whose fields are all primitives — `i32`, `f32`, `f64`, `bool`
96+
describes a fixed-layout struct: declared field order, natural alignment, the
97+
same layout on every supported target. That is what code crossing into native
98+
memory needs.
99+
100+
The syntax is parsed natively today. The full control-flow ownership pass that
101+
enforces every rule in [the language contract](../language/) is still being
102+
written, and [the implementation ledger](../implementation/) tracks exactly
103+
what is checked and what is only parsed. It is worth reading before you rely
104+
on a rule being enforced.
123105

124106
## An HTTP server
125107

126108
`Sxn.serve` hands your function a `Request` and expects a `Response` back —
127109
the same pair of objects a handler gets on Cloudflare Workers, Deno or Bun:
128110

129-
```js
130-
const server = Sxn.serve({ port: 3000 }, async (req) => {
111+
```sx
112+
const server = Sxn.serve({ port: 3000 }, async (req: Request): Promise<Response> => {
131113
const url = new URL(req.url);
132114
if (url.pathname === "/echo") return Response.json(await req.json());
133115
return new Response("hello from " + url.pathname);
@@ -137,7 +119,7 @@ console.log(`listening on ${server.url}`);
137119
```
138120

139121
```sh
140-
sxn server.js
122+
sxn server.sx
141123
```
142124

143125
```
@@ -148,16 +130,46 @@ listening on http://127.0.0.1:3000
148130
`server.port` then tells you which one it picked. `server.stop()` shuts the
149131
listener down, so one process can serve and then go on to do something else.
150132

133+
## JavaScript and TypeScript run too
134+
135+
Nothing above is required. `sxn` runs a plain `.js`, `.mjs`, `.cjs` or `.ts`
136+
file directly, and a `.sx` module can `import` any of them and vice versa. A
137+
`.sx` file that uses none of the extra syntax is just JavaScript with a
138+
different extension.
139+
140+
```js
141+
const runtime = typeof Sxn !== "undefined" ? "sxn " + Sxn.version : "something else";
142+
console.log(`hello from ${runtime}`);
143+
```
144+
145+
```sh
146+
sxn hello.js
147+
```
148+
149+
```
150+
hello from sxn 0.0.1
151+
```
152+
153+
TypeScript's erasable forms are all accepted: aliases, interfaces, `declare`,
154+
annotations, optional parameters, generics on functions, `as`/`satisfies`, and
155+
union types. `enum` and `namespace` are rejected on purpose rather than
156+
stripped, because both emit a real object at runtime in TypeScript, and
157+
quietly removing them would turn every use of their members into `undefined`:
158+
159+
```
160+
SyntaxError: unsupported keyword: enum
161+
```
162+
151163
## Precompiling
152164

153165
`sxn compile` writes bytecode that skips parsing on later runs:
154166

155167
```sh
156-
sxn compile app.js -o app.sxbc
168+
sxn compile app.sx -o app.sxbc
157169
sxn app.sxbc
158170
```
159171

160-
`sxn --compile-cache app.js` does the same thing automatically, building the
172+
`sxn --compile-cache app.sx` does the same thing automatically, building the
161173
cache on the first launch and reusing it afterwards. The measured gains, and
162174
the reason bytecode is not a safe format for untrusted input, are in
163175
[the bytecode spec](../bytecode/).

0 commit comments

Comments
 (0)