rip-lang 3.6.1 → 3.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -1
- package/README.md +42 -17
- package/docs/NOTES.md +93 -0
- package/docs/RIP-GUIDE.md +39 -1
- package/docs/RIP-INTERNALS.md +9 -9
- package/docs/RIP-LANG.md +213 -2
- package/docs/RIP-TYPES.md +98 -0
- package/docs/dist/rip.browser.js +976 -508
- package/docs/dist/rip.browser.min.js +215 -215
- package/docs/dist/rip.browser.min.js.br +0 -0
- package/docs/index.html +52 -31
- package/package.json +3 -3
- package/src/compiler.js +238 -151
- package/src/components.js +167 -169
- package/src/grammar/grammar.rip +12 -2
- package/src/lexer.js +77 -5
- package/src/parser.js +123 -120
- package/src/repl.js +4 -128
- package/src/types.js +416 -35
package/CHANGELOG.md
CHANGED
|
@@ -7,7 +7,33 @@ All notable changes to Rip will be documented in this file.
|
|
|
7
7
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
8
8
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
9
9
|
|
|
10
|
-
## [3.
|
|
10
|
+
## [3.7.0] - 2026-02-11
|
|
11
|
+
|
|
12
|
+
### Pipe Operator (`|>`)
|
|
13
|
+
|
|
14
|
+
- **First-arg insertion pipe** — `x |> fn` compiles to `fn(x)`, and `x |> fn(y)` compiles to `fn(x, y)`. Combines the simplicity of F#-style pipes with multi-arg support like Elixir — no placeholder syntax needed. Left-associative, chains naturally: `5 |> double |> add(1)`.
|
|
15
|
+
- Works with dotted references (`x |> Math.sqrt`, `x |> console.log`) and dotted calls (`x |> obj.method(y)` → `obj.method(x, y)`).
|
|
16
|
+
- Implicit calls close at pipe boundaries — `x |> fn 1 |> bar 2` works correctly without parens.
|
|
17
|
+
|
|
18
|
+
### Method Assignment (`.=`) — A Rip Original
|
|
19
|
+
|
|
20
|
+
- **Compound method assignment** — `x .= trim()` compiles to `x = x.trim()`. The method-call equivalent of `+=`. No other language has this. Combined with implicit `it`, enables concise transformation pipelines: `items .= filter -> it.active` then `items .= map -> it.name`.
|
|
21
|
+
|
|
22
|
+
### Prototype Operator (`::`)
|
|
23
|
+
|
|
24
|
+
- **CoffeeScript-style prototype access restored** — `String::trim` compiles to `String.prototype.trim`. Disambiguated from type annotations by spacing: `::` with no space is prototype, `::` with a space is a type annotation. Both coexist cleanly.
|
|
25
|
+
|
|
26
|
+
### `loop n` — Repeat N Times
|
|
27
|
+
|
|
28
|
+
- **Counted loop** — `loop 5 -> body` compiles to `for (let _i = 0; _i < 5; _i++) { body }`. Works with literals, variables, and expressions.
|
|
29
|
+
|
|
30
|
+
### Implicit `it` Parameter
|
|
31
|
+
|
|
32
|
+
- **Auto-injected parameter** — Arrow functions with no explicit params that reference `it` in the body automatically inject `it` as the parameter. `arr.filter -> it > 5` compiles to `arr.filter(function(it) { return (it > 5); })`. Works with both `->` and `=>`. Stops at nested function boundaries.
|
|
33
|
+
|
|
34
|
+
### Negative Indexing
|
|
35
|
+
|
|
36
|
+
- **Python-style negative indexing** — `arr[-1]` compiles to `arr.at(-1)`, `arr[-2]` to `arr.at(-2)`, etc. Works on arrays and strings. Optional variant: `arr?[-1]` → `arr?.at(-1)`. Only literal negative numbers trigger the transform; variable indexes pass through unchanged.
|
|
11
37
|
|
|
12
38
|
### Browser Runtime — Full Async/Await Support
|
|
13
39
|
|
package/README.md
CHANGED
|
@@ -9,15 +9,15 @@
|
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
|
-
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/version-3.
|
|
12
|
+
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/version-3.7.0-blue.svg" alt="Version"></a>
|
|
13
13
|
<a href="#zero-dependencies"><img src="https://img.shields.io/badge/dependencies-ZERO-brightgreen.svg" alt="Dependencies"></a>
|
|
14
|
-
<a href="#"><img src="https://img.shields.io/badge/tests-
|
|
14
|
+
<a href="#"><img src="https://img.shields.io/badge/tests-1219%2F1219-brightgreen.svg" alt="Tests"></a>
|
|
15
15
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg" alt="License"></a>
|
|
16
16
|
</p>
|
|
17
17
|
|
|
18
18
|
---
|
|
19
19
|
|
|
20
|
-
Rip is a modern language inspired by CoffeeScript. It compiles to **ES2022** (classes, `?.`, `??`, modules), adds about a **dozen new operators**, includes **built-in reactivity**, and sports a self-hosting compiler with **zero dependencies** — all in about 10,
|
|
20
|
+
Rip is a modern language inspired by CoffeeScript. It compiles to **ES2022** (classes, `?.`, `??`, modules), adds about a **dozen new operators**, includes **built-in reactivity**, and sports a self-hosting compiler with **zero dependencies** — all in about 10,800 lines of code.
|
|
21
21
|
|
|
22
22
|
> **No imports. No hooks. No dependency arrays. Just write code.**
|
|
23
23
|
|
|
@@ -38,7 +38,7 @@ get '/users/:id' -> # RESTful API endpoint, comma-less
|
|
|
38
38
|
|
|
39
39
|
**What makes Rip different:**
|
|
40
40
|
- **Modern output** — ES2022 with native classes, `?.`, `??`, modules
|
|
41
|
-
- **New operators** — `!`, `!?`, `//`, `%%`, `=~`, `.new()`, and more
|
|
41
|
+
- **New operators** — `!`, `!?`, `//`, `%%`, `=~`, `|>`, `.new()`, and more
|
|
42
42
|
- **Reactive operators** — `:=`, `~=`, `~>` as language syntax
|
|
43
43
|
- **Optional types** — `::` annotations, `::=` aliases, `.d.ts` emission
|
|
44
44
|
- **Zero dependencies** — everything included, even the parser generator
|
|
@@ -122,6 +122,21 @@ for x as iterable # ES6 for-of on any iterable
|
|
|
122
122
|
|
|
123
123
|
for x as! asyncIterable # Async iteration shorthand
|
|
124
124
|
console.log x # Equivalent to: for await x as asyncIterable
|
|
125
|
+
|
|
126
|
+
loop # Infinite loop (while true)
|
|
127
|
+
process!
|
|
128
|
+
loop 5 # Repeat N times
|
|
129
|
+
console.log "hi"
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Implicit `it`
|
|
133
|
+
|
|
134
|
+
Arrow functions with no params that reference `it` auto-inject it as the parameter:
|
|
135
|
+
|
|
136
|
+
```coffee
|
|
137
|
+
users.filter -> it.active # → users.filter(function(it) { ... })
|
|
138
|
+
names = users.map -> it.name # no need to name a throwaway variable
|
|
139
|
+
orders.filter -> it.total > 100 # works with any expression
|
|
125
140
|
```
|
|
126
141
|
|
|
127
142
|
### Reactivity
|
|
@@ -168,6 +183,7 @@ Compiles to `.js` (types erased) + `.d.ts` (types preserved) — full IDE suppor
|
|
|
168
183
|
| `!?` (otherwise) | `val !? 5` | Default only if `undefined` |
|
|
169
184
|
| `?` (existence) | `x?` | True if `x != null` |
|
|
170
185
|
| `?:` (ternary) | `x > 0 ? 'yes' : 'no'` | JS-style ternary expression |
|
|
186
|
+
| `if...else` (postfix) | `"yes" if cond else "no"` | Python-style ternary expression |
|
|
171
187
|
| `?.` `?.[]` `?.()` | `a?.b` `a?.[0]` `a?.()` | Optional chaining (ES6) |
|
|
172
188
|
| `?[]` `?()` | `a?[0]` `a?(x)` | Optional chaining shorthand |
|
|
173
189
|
| `??` | `a ?? b` | Nullish coalescing |
|
|
@@ -177,6 +193,15 @@ Compiles to `.js` (types erased) + `.d.ts` (types preserved) — full IDE suppor
|
|
|
177
193
|
| `=~` | `str =~ /Hello, (\w+)/` | Match (captures in `_`) |
|
|
178
194
|
| `[//, n]` | `str[/Hello, (\w+)/, 1]` | Extract capture n |
|
|
179
195
|
| `.new()` | `Dog.new()` | Ruby-style constructor |
|
|
196
|
+
| `::` (prototype) | `String::trim` | `String.prototype.trim` |
|
|
197
|
+
| `[-n]` (negative index) | `arr[-1]` | Last element via `.at()` |
|
|
198
|
+
| `*` (string repeat) | `"-" * 40` | String repeat via `.repeat()` |
|
|
199
|
+
| `<` `<=` (chained) | `1 < x < 10` | Chained comparisons |
|
|
200
|
+
| `\|>` (pipe) | `x \|> fn` or `x \|> fn(y)` | Pipe operator (first-arg insertion) |
|
|
201
|
+
| `not in` | `x not in arr` | Negated membership test |
|
|
202
|
+
| `not of` | `k not of obj` | Negated key existence |
|
|
203
|
+
| `.=` (method assign) | `x .= trim()` | `x = x.trim()` — compound method assignment |
|
|
204
|
+
| `*` (merge assign) | `*obj = {a: 1}` | `Object.assign(obj, {a: 1})` |
|
|
180
205
|
| `or return` | `x = get() or return err` | Guard clause (Ruby-style) |
|
|
181
206
|
| `?? throw` | `x = get() ?? throw err` | Nullish guard |
|
|
182
207
|
|
|
@@ -254,9 +279,9 @@ See [@rip-lang/ui](packages/ui/) for the full framework: file-based router, reac
|
|
|
254
279
|
| **Reactivity** | None | Built-in |
|
|
255
280
|
| **Dependencies** | Multiple | Zero |
|
|
256
281
|
| **Self-hosting** | No | Yes |
|
|
257
|
-
| **Lexer** | 3,558 LOC | 1,
|
|
258
|
-
| **Compiler** | 10,346 LOC | 3,
|
|
259
|
-
| **Total** | 17,760 LOC | ~10,
|
|
282
|
+
| **Lexer** | 3,558 LOC | 1,958 LOC |
|
|
283
|
+
| **Compiler** | 10,346 LOC | 3,378 LOC |
|
|
284
|
+
| **Total** | 17,760 LOC | ~10,800 LOC |
|
|
260
285
|
|
|
261
286
|
Smaller codebase, modern output, built-in reactivity.
|
|
262
287
|
|
|
@@ -291,25 +316,25 @@ await rip("res = fetch! 'https://api.example.com/todos/1'; res.json!") // → {
|
|
|
291
316
|
|
|
292
317
|
```
|
|
293
318
|
Source -> Lexer -> emitTypes -> Parser -> S-Expressions -> Codegen -> JavaScript
|
|
294
|
-
(1,
|
|
319
|
+
(1,958) (types.js) (359) ["=", "x", 42] (3,378) + source map
|
|
295
320
|
```
|
|
296
321
|
|
|
297
322
|
Simple arrays (with `.loc`) instead of AST node classes. The compiler is self-hosting — `bun run parser` rebuilds from source.
|
|
298
323
|
|
|
299
324
|
| Component | File | Lines |
|
|
300
325
|
|-----------|------|-------|
|
|
301
|
-
| Lexer + Rewriter | `src/lexer.js` | 1,
|
|
302
|
-
| Compiler + Codegen | `src/compiler.js` | 3,
|
|
303
|
-
| Type System | `src/types.js` |
|
|
326
|
+
| Lexer + Rewriter | `src/lexer.js` | 1,958 |
|
|
327
|
+
| Compiler + Codegen | `src/compiler.js` | 3,378 |
|
|
328
|
+
| Type System | `src/types.js` | 1,099 |
|
|
304
329
|
| Component System | `src/components.js` | 1,240 |
|
|
305
330
|
| Source Maps | `src/sourcemaps.js` | 122 |
|
|
306
|
-
| Parser (generated) | `src/parser.js` |
|
|
307
|
-
| Grammar | `src/grammar/grammar.rip` |
|
|
331
|
+
| Parser (generated) | `src/parser.js` | 359 |
|
|
332
|
+
| Grammar | `src/grammar/grammar.rip` | 945 |
|
|
308
333
|
| Parser Generator | `src/grammar/solar.rip` | 916 |
|
|
309
|
-
| REPL | `src/repl.js` |
|
|
334
|
+
| REPL | `src/repl.js` | 582 |
|
|
310
335
|
| Browser Entry | `src/browser.js` | 119 |
|
|
311
336
|
| Tags | `src/tags.js` | 63 |
|
|
312
|
-
| **Total** | | **~10,
|
|
337
|
+
| **Total** | | **~10,774** |
|
|
313
338
|
|
|
314
339
|
---
|
|
315
340
|
|
|
@@ -319,7 +344,7 @@ Rip includes optional packages for full-stack development:
|
|
|
319
344
|
|
|
320
345
|
| Package | Version | Purpose |
|
|
321
346
|
|---------|---------|---------|
|
|
322
|
-
| [rip-lang](https://www.npmjs.com/package/rip-lang) | 3.
|
|
347
|
+
| [rip-lang](https://www.npmjs.com/package/rip-lang) | 3.7.0 | Core language compiler |
|
|
323
348
|
| [@rip-lang/api](packages/api/) | 1.1.4 | HTTP framework (Sinatra-style routing, 37 validators) |
|
|
324
349
|
| [@rip-lang/server](packages/server/) | 1.1.3 | Multi-worker app server (hot reload, HTTPS, mDNS) |
|
|
325
350
|
| [@rip-lang/db](packages/db/) | 1.1.2 | DuckDB server with official UI (pure Bun FFI) |
|
|
@@ -365,7 +390,7 @@ rip file.rip # Run
|
|
|
365
390
|
rip -c file.rip # Compile
|
|
366
391
|
rip -t file.rip # Tokens
|
|
367
392
|
rip -s file.rip # S-expressions
|
|
368
|
-
bun run test #
|
|
393
|
+
bun run test # 1219 tests
|
|
369
394
|
bun run parser # Rebuild parser
|
|
370
395
|
bun run browser # Build browser bundle
|
|
371
396
|
```
|
package/docs/NOTES.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<img src="https://raw.githubusercontent.com/shreeve/rip-lang/main/docs/rip.png" style="width:50px" /> <br>
|
|
2
|
+
|
|
3
|
+
# Rip — Notes
|
|
4
|
+
|
|
5
|
+
Ideas, future plans, and design thoughts for Rip.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Standard Library (`stdlib`)
|
|
10
|
+
|
|
11
|
+
Rip is a zero-dependency language, but a small standard library of useful
|
|
12
|
+
utilities would save users from writing the same one-liners in every project.
|
|
13
|
+
These are **not** language features — they're plain functions that could ship
|
|
14
|
+
as a prelude or optional import.
|
|
15
|
+
|
|
16
|
+
### Candidates
|
|
17
|
+
|
|
18
|
+
```coffee
|
|
19
|
+
# Printing (Ruby's p)
|
|
20
|
+
p = console.log
|
|
21
|
+
|
|
22
|
+
# Exit with optional code (uses implicit `it`)
|
|
23
|
+
exit = -> process.exit(it)
|
|
24
|
+
|
|
25
|
+
# Tap — call a function for side effects, return the original value
|
|
26
|
+
# Useful in pipe chains: data |> tap(console.log) |> process
|
|
27
|
+
tap = (v, fn) -> fn(v); v
|
|
28
|
+
|
|
29
|
+
# Identity — returns its argument unchanged
|
|
30
|
+
# Useful as a default callback: items.filter(id)
|
|
31
|
+
id = -> it
|
|
32
|
+
|
|
33
|
+
# No-op — does nothing
|
|
34
|
+
# Useful as a default handler: onClick ?= noop
|
|
35
|
+
noop = ->
|
|
36
|
+
|
|
37
|
+
# String method aliases (shorter names for common checks)
|
|
38
|
+
String::starts = String::startsWith
|
|
39
|
+
String::ends = String::endsWith
|
|
40
|
+
String::has = String::includes
|
|
41
|
+
|
|
42
|
+
# Clamp a value to a range
|
|
43
|
+
clamp = (v, lo, hi) -> Math.min(Math.max(v, lo), hi)
|
|
44
|
+
|
|
45
|
+
# Sleep for N milliseconds (returns a Promise)
|
|
46
|
+
sleep = (ms) -> new Promise (resolve) -> setTimeout resolve, ms
|
|
47
|
+
|
|
48
|
+
# Times helper — call a function N times, collect results
|
|
49
|
+
times = (n, fn) -> (fn(i) for i in [0...n])
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Design Questions
|
|
53
|
+
|
|
54
|
+
- **Prelude vs import?** Should these be injected automatically (like Go's
|
|
55
|
+
`fmt` or Rip's reactive runtime), or explicitly imported (`import { p, tap }
|
|
56
|
+
from '@rip-lang/std'`)? Leaning toward explicit — Rip's philosophy is zero
|
|
57
|
+
magic in the output.
|
|
58
|
+
|
|
59
|
+
- **Scope?** Keep it tiny. A stdlib that grows to 500 functions defeats the
|
|
60
|
+
purpose. Each entry should save real keystrokes on something people do
|
|
61
|
+
constantly.
|
|
62
|
+
|
|
63
|
+
- **Node vs Browser?** Some helpers (like `exit`) are Node-only. Others (like
|
|
64
|
+
`p`, `tap`, `sleep`) work everywhere. May want to split into `std` (universal)
|
|
65
|
+
and `std/node` (server-only).
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Future Syntax Ideas
|
|
70
|
+
|
|
71
|
+
Ideas that have been discussed but not yet implemented. Each would need
|
|
72
|
+
design discussion before building.
|
|
73
|
+
|
|
74
|
+
- **`defer`** — Go-style cleanup that runs when the function exits. Compiles
|
|
75
|
+
to try/finally. `defer file.close()`.
|
|
76
|
+
|
|
77
|
+
- **`is a` / `isnt a`** — Readable instanceof. `x is a String` →
|
|
78
|
+
`x instanceof String`.
|
|
79
|
+
|
|
80
|
+
- **`.starts?` / `.ends?` / `.has?`** — Ruby-style question-mark methods.
|
|
81
|
+
`url.starts? "https"` → `url.startsWith("https")`.
|
|
82
|
+
|
|
83
|
+
- **Pattern matching** — `match value` with destructuring arms. Big feature,
|
|
84
|
+
needs careful design.
|
|
85
|
+
|
|
86
|
+
- **Reactive resource operator (`~>?`)** — Language-level `createResource`.
|
|
87
|
+
`user ~>? fetch!("/api/users/#{id}").json!` gives `user.loading`,
|
|
88
|
+
`user.error`, `user.data`. Park until real-world usage shows demand.
|
|
89
|
+
|
|
90
|
+
- **Pipe operator (`|>`) — Hack-style placeholder** — Currently Rip uses
|
|
91
|
+
Elixir-style first-arg insertion. A `%` placeholder for arbitrary position
|
|
92
|
+
(`data |> fn(1, %, 3)`) could be added later if needed. Current design
|
|
93
|
+
covers 95%+ of cases.
|
package/docs/RIP-GUIDE.md
CHANGED
|
@@ -117,6 +117,7 @@ else
|
|
|
117
117
|
|
|
118
118
|
# Ternary
|
|
119
119
|
status = active ? "on" : "off"
|
|
120
|
+
label = "big" if x > 5 else "small"
|
|
120
121
|
|
|
121
122
|
# Postfix
|
|
122
123
|
console.log "hi" if ready
|
|
@@ -147,11 +148,26 @@ for key, value of object
|
|
|
147
148
|
for i in [1..10] # inclusive (1 to 10)
|
|
148
149
|
for i in [1...10] # exclusive (1 to 9)
|
|
149
150
|
|
|
151
|
+
# Loop N times
|
|
152
|
+
loop 5
|
|
153
|
+
console.log "hi"
|
|
154
|
+
|
|
150
155
|
# Comprehensions
|
|
151
156
|
squares = (x * x for x in [1..10])
|
|
152
157
|
evens = (x for x in items when x % 2 is 0)
|
|
153
158
|
```
|
|
154
159
|
|
|
160
|
+
### Implicit `it`
|
|
161
|
+
|
|
162
|
+
Arrow functions with no params that reference `it` auto-inject it as the parameter:
|
|
163
|
+
|
|
164
|
+
```coffee
|
|
165
|
+
users.filter -> it.active # instead of (u) -> u.active
|
|
166
|
+
names = users.map -> it.name # instead of (u) -> u.name
|
|
167
|
+
orders.filter -> it.total > 100 # works with any expression
|
|
168
|
+
items.map => it.toUpperCase() # works with fat arrows too
|
|
169
|
+
```
|
|
170
|
+
|
|
155
171
|
---
|
|
156
172
|
|
|
157
173
|
## Operators
|
|
@@ -174,6 +190,15 @@ Rip extends JavaScript with powerful operators:
|
|
|
174
190
|
| `:=` | State | `count := 0` | Reactive signal |
|
|
175
191
|
| `~=` | Computed | `doubled ~= x * 2` | Reactive computed |
|
|
176
192
|
| `~>` | Effect | `~> console.log x` | Reactive side effect |
|
|
193
|
+
| `::` | Prototype | `String::trim` | `String.prototype.trim` |
|
|
194
|
+
| `[-n]` | Negative index | `arr[-1]` | `arr.at(-1)` |
|
|
195
|
+
| `*` | String repeat | `"-" * 40` | `"-".repeat(40)` |
|
|
196
|
+
| `<` `<=` | Chained | `1 < x < 10` | `(1 < x) && (x < 10)` |
|
|
197
|
+
| `.=` | Method assign | `x .= trim()` | `x = x.trim()` |
|
|
198
|
+
| `*` | Merge assign | `*obj = {a: 1}` | `Object.assign(obj, {a: 1})` |
|
|
199
|
+
| `not in` | Not in | `x not in arr` | Negated membership |
|
|
200
|
+
| `not of` | Not of | `k not of obj` | Negated key existence |
|
|
201
|
+
| `if...else` | Postfix ternary | `"a" if x else "b"` | `x ? "a" : "b"` |
|
|
177
202
|
| `**` | Power | `2 ** 10` | `1024` |
|
|
178
203
|
| `..` | Range | `[1..5]` | Inclusive range |
|
|
179
204
|
| `...` | Spread/rest | `[...a, ...b]` | ES6 spread |
|
|
@@ -211,6 +236,19 @@ if text =~ /Hello, (\w+)/
|
|
|
211
236
|
domain = "user@example.com"[/@(.+)$/, 1] # "example.com"
|
|
212
237
|
```
|
|
213
238
|
|
|
239
|
+
### Pipe Operator (`|>`)
|
|
240
|
+
|
|
241
|
+
Pipes a value into a function as the first argument. Chains left-to-right:
|
|
242
|
+
|
|
243
|
+
```coffee
|
|
244
|
+
5 |> double # → double(5)
|
|
245
|
+
5 |> add(3) # → add(5, 3)
|
|
246
|
+
5 |> double |> add(1) # → add(double(5), 1)
|
|
247
|
+
data |> JSON.stringify(null, 2) # → JSON.stringify(data, null, 2)
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
If the right side is a bare reference, it's called with the piped value. If the right side is already a call, the piped value is inserted as the first argument. This is the Elixir-style pipe — cleaner than F# (multi-arg works) and simpler than Hack (no placeholder needed).
|
|
251
|
+
|
|
214
252
|
### Guard Clauses
|
|
215
253
|
|
|
216
254
|
```coffee
|
|
@@ -633,7 +671,7 @@ rip -d file.rip # Generate .d.ts
|
|
|
633
671
|
rip -t file.rip # Show tokens
|
|
634
672
|
rip -s file.rip # Show S-expressions
|
|
635
673
|
rip -q -c file.rip # Quiet (no headers)
|
|
636
|
-
bun run test # Run test suite
|
|
674
|
+
bun run test # Run test suite
|
|
637
675
|
```
|
|
638
676
|
|
|
639
677
|
---
|
package/docs/RIP-INTERNALS.md
CHANGED
|
@@ -45,7 +45,7 @@ class BinaryOp {
|
|
|
45
45
|
["+", left, right] // That's it!
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
**Result:** CoffeeScript's compiler is 17,760 LOC. Rip's is ~10,
|
|
48
|
+
**Result:** CoffeeScript's compiler is 17,760 LOC. Rip's is ~10,800 LOC — smaller, yet includes a complete reactive runtime, type system, component system, and source maps.
|
|
49
49
|
|
|
50
50
|
> **Transform the IR (s-expressions), not the output (strings).**
|
|
51
51
|
|
|
@@ -66,7 +66,7 @@ console.log(code);
|
|
|
66
66
|
| Feature | CoffeeScript | Rip |
|
|
67
67
|
|---------|-------------|------|
|
|
68
68
|
| Optional chaining | 4 soak operators | ES6 `?.` / `?.[]` / `?.()` + shorthand `?[]` / `?()` |
|
|
69
|
-
| Ternary | No | `x ? a : b` |
|
|
69
|
+
| Ternary | No | `x ? a : b` and `a if x else b` |
|
|
70
70
|
| Regex features | Basic | Ruby-style (`=~`, indexing, captures in `_`) |
|
|
71
71
|
| Async shorthand | No | Dammit operator (`!`) |
|
|
72
72
|
| Void functions | No | `def fn!` |
|
|
@@ -77,7 +77,7 @@ console.log(code);
|
|
|
77
77
|
| Dependencies | Multiple | **Zero** |
|
|
78
78
|
| Parser generator | External (Jison) | **Built-in (Solar)** |
|
|
79
79
|
| Self-hosting | No | **Yes** |
|
|
80
|
-
| Total LOC | 17,760 | ~10,
|
|
80
|
+
| Total LOC | 17,760 | ~10,800 |
|
|
81
81
|
|
|
82
82
|
## Design Principles
|
|
83
83
|
|
|
@@ -93,7 +93,7 @@ console.log(code);
|
|
|
93
93
|
|
|
94
94
|
```
|
|
95
95
|
Source Code → Lexer → emitTypes → Parser → S-Expressions → Codegen → JavaScript
|
|
96
|
-
(1,
|
|
96
|
+
(1,958) (types.js) (359) (arrays + .loc) (3,378) + source map
|
|
97
97
|
↓
|
|
98
98
|
file.d.ts (when types: "emit")
|
|
99
99
|
```
|
|
@@ -102,14 +102,14 @@ Source Code → Lexer → emitTypes → Parser → S-Expressions → C
|
|
|
102
102
|
|
|
103
103
|
| File | Purpose | Lines | Modify? |
|
|
104
104
|
|------|---------|-------|---------|
|
|
105
|
-
| `src/lexer.js` | Lexer + Rewriter | 1,
|
|
106
|
-
| `src/compiler.js` | Compiler + Code Generator | 3,
|
|
107
|
-
| `src/types.js` | Type System (lexer sidecar) |
|
|
105
|
+
| `src/lexer.js` | Lexer + Rewriter | 1,958 | Yes |
|
|
106
|
+
| `src/compiler.js` | Compiler + Code Generator | 3,378 | Yes |
|
|
107
|
+
| `src/types.js` | Type System (lexer sidecar) | 1,099 | Yes |
|
|
108
108
|
| `src/components.js` | Component System (compiler sidecar) | 1,240 | Yes |
|
|
109
109
|
| `src/sourcemaps.js` | Source Map V3 Generator | 122 | Yes |
|
|
110
110
|
| `src/tags.js` | HTML Tag Classification | 63 | Yes |
|
|
111
111
|
| `src/parser.js` | Generated parser | 357 | No (auto-gen) |
|
|
112
|
-
| `src/grammar/grammar.rip` | Grammar specification |
|
|
112
|
+
| `src/grammar/grammar.rip` | Grammar specification | 945 | Yes (carefully) |
|
|
113
113
|
| `src/grammar/solar.rip` | Parser generator | 916 | No |
|
|
114
114
|
|
|
115
115
|
## Example Flow
|
|
@@ -579,4 +579,4 @@ rip> .js # Toggle JS display
|
|
|
579
579
|
|
|
580
580
|
---
|
|
581
581
|
|
|
582
|
-
*Rip 3.
|
|
582
|
+
*Rip 3.7 — 1,219 tests passing — Zero dependencies — Self-hosting — ~10,800 LOC*
|
package/docs/RIP-LANG.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Rip Language Reference
|
|
4
4
|
|
|
5
|
-
Rip is a modern reactive language that compiles to ES2022 JavaScript. It combines CoffeeScript's elegant syntax with built-in reactivity primitives. Zero dependencies, self-hosting, ~10,
|
|
5
|
+
Rip is a modern reactive language that compiles to ES2022 JavaScript. It combines CoffeeScript's elegant syntax with built-in reactivity primitives. Zero dependencies, self-hosting, ~10,800 LOC.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -121,6 +121,10 @@ else
|
|
|
121
121
|
status = active ? "on" : "off"
|
|
122
122
|
result = x > 5 ? "big" : "small"
|
|
123
123
|
|
|
124
|
+
# Ternary (Python-style postfix)
|
|
125
|
+
status = "active" if online else "offline"
|
|
126
|
+
label = "big" if x > 5 else "small"
|
|
127
|
+
|
|
124
128
|
# NOTE: Subscript in ternary true-branch needs parentheses
|
|
125
129
|
item = found ? (arr[0]) : default
|
|
126
130
|
|
|
@@ -227,6 +231,15 @@ until done
|
|
|
227
231
|
loop
|
|
228
232
|
data = fetch()
|
|
229
233
|
break if data.complete
|
|
234
|
+
|
|
235
|
+
# Loop N times
|
|
236
|
+
loop 5
|
|
237
|
+
console.log "hi"
|
|
238
|
+
# Compiles to: for (let _i = 0; _i < 5; _i++) { ... }
|
|
239
|
+
|
|
240
|
+
# Loop with variable or expression
|
|
241
|
+
loop retries
|
|
242
|
+
attempt!
|
|
230
243
|
```
|
|
231
244
|
|
|
232
245
|
## Comprehensions
|
|
@@ -275,6 +288,7 @@ Multiple lines
|
|
|
275
288
|
| `of` | `k of obj` | Object key existence |
|
|
276
289
|
| `?` (postfix) | `a?` | Existence check (`a != null`) |
|
|
277
290
|
| `?` (ternary) | `a ? b : c` | Ternary conditional |
|
|
291
|
+
| `if...else` (postfix) | `b if a else c` | Python-style ternary |
|
|
278
292
|
| `?.` `?.[]` `?.()` | `a?.b` `a?.[0]` `a?.()` | Optional chaining (ES6) |
|
|
279
293
|
| `?[]` `?()` | `a?[0]` `a?(x)` | Optional chaining shorthand |
|
|
280
294
|
| `??` | `a ?? b` | Nullish coalescing |
|
|
@@ -293,6 +307,15 @@ Multiple lines
|
|
|
293
307
|
| `!` | Void | `def process!` | Suppresses implicit return |
|
|
294
308
|
| `!?` | Otherwise | `val !? 5` | Default if undefined or throws |
|
|
295
309
|
| `=~` | Match | `str =~ /pat/` | Ruby-style regex match, captures in `_` |
|
|
310
|
+
| `::` | Prototype | `String::trim` | `String.prototype.trim` |
|
|
311
|
+
| `[-n]` | Negative index | `arr[-1]` | `arr.at(-1)` |
|
|
312
|
+
| `*` | String repeat | `"-" * 40` | `"-".repeat(40)` |
|
|
313
|
+
| `<` `<=` | Chained comparison | `1 < x < 10` | `(1 < x) && (x < 10)` |
|
|
314
|
+
| `\|>` | Pipe | `x \|> fn` or `x \|> fn(y)` | `fn(x)` or `fn(x, y)` |
|
|
315
|
+
| `.=` | Method assign | `x .= trim()` | `x = x.trim()` |
|
|
316
|
+
| `*` | Merge assign | `*obj = {a: 1}` | `Object.assign(obj, {a: 1})` |
|
|
317
|
+
| `not in` | Not in | `x not in arr` | Negated membership test |
|
|
318
|
+
| `not of` | Not of | `k not of obj` | Negated key existence |
|
|
296
319
|
|
|
297
320
|
## Assignment Operators
|
|
298
321
|
|
|
@@ -333,6 +356,10 @@ status = active ? 'on' : 'off'
|
|
|
333
356
|
result = valid ? obj.field : null
|
|
334
357
|
output = ready ? compute() : fallback
|
|
335
358
|
|
|
359
|
+
# Python-style postfix ternary
|
|
360
|
+
status = "active" if online else "offline"
|
|
361
|
+
label = "big" if x > 5 else "small"
|
|
362
|
+
|
|
336
363
|
# Nested
|
|
337
364
|
level = score > 90 ? 'A' : score > 80 ? 'B' : score > 70 ? 'C' : 'F'
|
|
338
365
|
|
|
@@ -349,6 +376,155 @@ result = riskyOperation() !? "default"
|
|
|
349
376
|
# If riskyOperation() throws or returns null/undefined, result = "default"
|
|
350
377
|
```
|
|
351
378
|
|
|
379
|
+
## Method Assignment (`.=`)
|
|
380
|
+
|
|
381
|
+
A Rip original. Compound assignment for method calls — apply a method to a
|
|
382
|
+
variable and assign the result back in one step:
|
|
383
|
+
|
|
384
|
+
```coffee
|
|
385
|
+
# Without .= — repeat the variable name every time
|
|
386
|
+
items = items.filter -> it.active
|
|
387
|
+
items = items.map -> it.name
|
|
388
|
+
items = items.sort (a, b) -> a.localeCompare b
|
|
389
|
+
str = str.trim()
|
|
390
|
+
str = str.toLowerCase()
|
|
391
|
+
|
|
392
|
+
# With .= — name it once, transform in place
|
|
393
|
+
items .= filter -> it.active
|
|
394
|
+
items .= map -> it.name
|
|
395
|
+
items .= sort (a, b) -> a.localeCompare b
|
|
396
|
+
str .= trim()
|
|
397
|
+
str .= toLowerCase()
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
`x .= method(args)` compiles to `x = x.method(args)`. It's the method-call
|
|
401
|
+
equivalent of `+=` — just as `x += 5` means `x = x + 5`, `x .= trim()`
|
|
402
|
+
means `x = x.trim()`.
|
|
403
|
+
|
|
404
|
+
This operator is unique to Rip. Other languages have `+=`, `-=`, `*=`, and
|
|
405
|
+
other arithmetic compound assignments, but none extend the concept to method
|
|
406
|
+
calls. Combined with implicit `it`, this enables remarkably concise data
|
|
407
|
+
transformation pipelines:
|
|
408
|
+
|
|
409
|
+
```coffee
|
|
410
|
+
users .= filter -> it.active
|
|
411
|
+
users .= map -> it.name
|
|
412
|
+
users .= sort()
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
Works with any method — built-in or custom, with or without arguments.
|
|
416
|
+
|
|
417
|
+
## Merge Assignment (`*`)
|
|
418
|
+
|
|
419
|
+
A Rip original. Merge properties into an existing object without repeating
|
|
420
|
+
its name:
|
|
421
|
+
|
|
422
|
+
```coffee
|
|
423
|
+
# Without * — repeat the object name or use verbose Object.assign
|
|
424
|
+
Object.assign config,
|
|
425
|
+
host: "localhost"
|
|
426
|
+
port: 3000
|
|
427
|
+
debug: true
|
|
428
|
+
|
|
429
|
+
# With * — clean and direct
|
|
430
|
+
*config =
|
|
431
|
+
host: "localhost"
|
|
432
|
+
port: 3000
|
|
433
|
+
debug: true
|
|
434
|
+
|
|
435
|
+
# Single line
|
|
436
|
+
*opts = {method: "POST", body: data}
|
|
437
|
+
|
|
438
|
+
# Dotted paths
|
|
439
|
+
*el.style =
|
|
440
|
+
color: "red"
|
|
441
|
+
fontSize: "14px"
|
|
442
|
+
|
|
443
|
+
# Merge user overrides into defaults
|
|
444
|
+
*defaults = userConfig
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
`*target = value` compiles to `Object.assign(target, value)`. The `*` reads
|
|
448
|
+
as "spread these into" — the same concept as `...` spread but as an
|
|
449
|
+
assignment. This is unique to Rip.
|
|
450
|
+
|
|
451
|
+
Common use cases: config objects, options bags, state initialization, DOM
|
|
452
|
+
styling, merging defaults with overrides — anywhere you're setting multiple
|
|
453
|
+
properties on an existing object.
|
|
454
|
+
|
|
455
|
+
## Prototype Operator (`::`)
|
|
456
|
+
|
|
457
|
+
Access `.prototype` with `::` (CoffeeScript-style). Disambiguated from type annotations by spacing:
|
|
458
|
+
|
|
459
|
+
```coffee
|
|
460
|
+
# Prototype access (no space after ::)
|
|
461
|
+
String::starts = String::startsWith
|
|
462
|
+
String::ends = String::endsWith
|
|
463
|
+
String::has = String::includes
|
|
464
|
+
|
|
465
|
+
# Now you can use them
|
|
466
|
+
"hello".starts "he" # true
|
|
467
|
+
"hello.rip".ends ".rip" # true
|
|
468
|
+
"error: bad".has "error" # true
|
|
469
|
+
|
|
470
|
+
# Define new prototype methods
|
|
471
|
+
String::shout = -> @toUpperCase() + "!"
|
|
472
|
+
"hello".shout() # "HELLO!"
|
|
473
|
+
|
|
474
|
+
# Type annotations (space after ::) — unaffected
|
|
475
|
+
name:: string = "Alice"
|
|
476
|
+
def greet(name:: string):: string
|
|
477
|
+
"Hello, #{name}!"
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
The rule is simple: `::` with **no space** before an identifier is prototype access. `::` with a **space** is a type annotation.
|
|
481
|
+
|
|
482
|
+
## Negative Indexing
|
|
483
|
+
|
|
484
|
+
Literal negative indexes compile to `.at()` for Python-style access from the end:
|
|
485
|
+
|
|
486
|
+
```coffee
|
|
487
|
+
arr = [10, 20, 30, 40, 50]
|
|
488
|
+
|
|
489
|
+
arr[-1] # → arr.at(-1) — 50 (last)
|
|
490
|
+
arr[-2] # → arr.at(-2) — 40 (second to last)
|
|
491
|
+
str[-1] # works on strings too
|
|
492
|
+
|
|
493
|
+
arr?[-1] # → arr?.at(-1) — optional variant
|
|
494
|
+
|
|
495
|
+
# Positive and variable indexes are unchanged
|
|
496
|
+
arr[0] # → arr[0] — normal access
|
|
497
|
+
arr[i] # → arr[i] — variable index
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
Only literal negative numbers trigger the `.at()` transform. Variable indexes pass through as-is.
|
|
501
|
+
|
|
502
|
+
## Pipe Operator (`|>`)
|
|
503
|
+
|
|
504
|
+
Pipes a value into a function as its first argument. Chains left-to-right:
|
|
505
|
+
|
|
506
|
+
```coffee
|
|
507
|
+
# Simple reference — value becomes the sole argument
|
|
508
|
+
5 |> double # → double(5)
|
|
509
|
+
10 |> Math.sqrt # → Math.sqrt(10)
|
|
510
|
+
"hello" |> console.log # → console.log("hello")
|
|
511
|
+
|
|
512
|
+
# Multi-arg — value is inserted as the FIRST argument
|
|
513
|
+
5 |> add(3) # → add(5, 3)
|
|
514
|
+
data |> filter(isActive) # → filter(data, isActive)
|
|
515
|
+
"World" |> greet("!") # → greet("World", "!")
|
|
516
|
+
|
|
517
|
+
# Chaining — reads left-to-right like a pipeline
|
|
518
|
+
5 |> double |> add(1) |> console.log
|
|
519
|
+
# → console.log(add(double(5), 1))
|
|
520
|
+
|
|
521
|
+
# Works with dotted methods
|
|
522
|
+
users |> Array.from # → Array.from(users)
|
|
523
|
+
data |> JSON.stringify(null, 2) # → JSON.stringify(data, null, 2)
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
This is the **Elixir-style** pipe — strictly better than F#'s (which only supports bare function references) and cleaner than Hack's (which requires a `%` placeholder). No special syntax to learn; if the right side is a call, the left value goes first.
|
|
527
|
+
|
|
352
528
|
---
|
|
353
529
|
|
|
354
530
|
# 4. Functions
|
|
@@ -420,6 +596,41 @@ def findUser(id)
|
|
|
420
596
|
null
|
|
421
597
|
```
|
|
422
598
|
|
|
599
|
+
## Implicit `it` Parameter
|
|
600
|
+
|
|
601
|
+
Arrow functions with no explicit parameters that reference `it` in the body automatically inject `it` as the parameter:
|
|
602
|
+
|
|
603
|
+
```coffee
|
|
604
|
+
# Without `it` — must name a throwaway variable
|
|
605
|
+
users.filter (u) -> u.active
|
|
606
|
+
names = users.map (u) -> u.name
|
|
607
|
+
|
|
608
|
+
# With `it` — cleaner
|
|
609
|
+
users.filter -> it.active
|
|
610
|
+
names = users.map -> it.name
|
|
611
|
+
orders.filter -> it.total > 100
|
|
612
|
+
|
|
613
|
+
# Works with fat arrows too
|
|
614
|
+
items.map => it.toUpperCase()
|
|
615
|
+
|
|
616
|
+
# Nested arrows — each level gets its own `it`
|
|
617
|
+
# Only the innermost param-less arrow with `it` is affected
|
|
618
|
+
groups.map -> it.items.filter -> it.active
|
|
619
|
+
|
|
620
|
+
# Explicit params still work normally
|
|
621
|
+
items.sort (a, b) -> a - b
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
Compiles to standard JavaScript — `it` becomes a regular function parameter:
|
|
625
|
+
|
|
626
|
+
```coffee
|
|
627
|
+
arr.filter -> it > 5
|
|
628
|
+
# → arr.filter(function(it) { return (it > 5); })
|
|
629
|
+
|
|
630
|
+
arr.map => it.name
|
|
631
|
+
# → arr.map(it => it.name)
|
|
632
|
+
```
|
|
633
|
+
|
|
423
634
|
## Calling Functions
|
|
424
635
|
|
|
425
636
|
```coffee
|
|
@@ -1165,4 +1376,4 @@ count = 10 # Logs: "Count: 10, Doubled: 20"
|
|
|
1165
1376
|
|
|
1166
1377
|
---
|
|
1167
1378
|
|
|
1168
|
-
*Rip 3.
|
|
1379
|
+
*Rip 3.7 — 1,219 tests passing — Zero dependencies — Self-hosting — ~10,800 LOC*
|