rip-lang 2.9.0 → 2.9.2
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 +39 -0
- package/README.md +32 -2
- package/docs/BROWSER.md +8 -8
- package/docs/GUIDE.md +13 -13
- package/docs/INTERNALS.md +11 -11
- package/docs/RATIONALE.md +180 -0
- package/docs/REACTIVITY.md +5 -5
- package/docs/TYPES.md +1132 -0
- package/docs/dist/rip.browser.js +12 -36
- package/docs/dist/rip.browser.min.js +87 -87
- package/docs/dist/rip.browser.min.js.br +0 -0
- package/docs/examples/README.md +21 -142
- package/docs/examples/arrows.rip +3 -13
- package/docs/examples/module.rip +41 -31
- package/docs/examples/reactivity.rip +48 -0
- package/package.json +1 -1
- package/src/compiler.js +13 -3
- package/src/grammar/grammar.rip +3 -3
- package/src/grammar/solar.rip +2 -2
- package/src/lexer.js +0 -59
- package/src/parser.js +1 -1
- package/docs/PHILOSOPHY.md +0 -569
- package/docs/WHY-NOT-COFFEESCRIPT.md +0 -186
- package/docs/WHY-YES-RIP.md +0 -757
- package/docs/examples/object-syntax.rip +0 -74
- package/docs/examples/prototype.rip +0 -30
- package/docs/examples/sexpr.rip +0 -128
- package/docs/examples/use-loader.js +0 -9
- package/docs/examples/utils.rip +0 -20
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,45 @@ 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
|
+
## [2.9.0] - 2026-02-05
|
|
11
|
+
|
|
12
|
+
### Compiler Fix
|
|
13
|
+
|
|
14
|
+
- **Interpolated string object keys**: `{"#{k}": v}` now correctly compiles to `{[\`${k}\`]: v}` instead of invalid `{\`${k}\`: v}`. Template literals in object key position are wrapped in computed property syntax.
|
|
15
|
+
|
|
16
|
+
### @rip-lang/db — DuckDB Server with Official UI
|
|
17
|
+
|
|
18
|
+
Major milestone: complete DuckDB HTTP server with the official DuckDB UI.
|
|
19
|
+
|
|
20
|
+
- **Pure Bun FFI driver** — Direct calls to DuckDB's C API, no npm packages, no Zig
|
|
21
|
+
- **Modern chunk-based API** — Uses `duckdb_fetch_chunk` + `duckdb_vector_get_data` for direct columnar memory reads. No deprecated `duckdb_value_*` functions.
|
|
22
|
+
- **DuckDB UI loads instantly** — Full binary serialization protocol, proper COOP/COEP headers, SSE keepalive, version handshake
|
|
23
|
+
- **Complete type support** — DECIMAL (exact precision), ENUM (dictionary lookup), UUID (native hugeint), LIST/STRUCT/MAP (child vector traversal), all timestamp variants
|
|
24
|
+
- **Timestamp policy** — All timestamps normalized to UTC Date objects; TIMESTAMPTZ recommended
|
|
25
|
+
- **Binary protocol** — Native 16-byte UUID serialization, uint64-aligned validity bitmaps, proper TypeInfo encoding
|
|
26
|
+
|
|
27
|
+
### @rip-lang/api — v1.0.0
|
|
28
|
+
|
|
29
|
+
- Published as stable 1.0.0
|
|
30
|
+
- Polished README with feature table and cross-references
|
|
31
|
+
|
|
32
|
+
### @rip-lang/server — v1.0.0
|
|
33
|
+
|
|
34
|
+
- Published as stable 1.0.0
|
|
35
|
+
- Added proper dependencies (rip-lang + @rip-lang/api)
|
|
36
|
+
- Polished README with feature table and cross-references
|
|
37
|
+
|
|
38
|
+
### Housekeeping
|
|
39
|
+
|
|
40
|
+
- Removed all Zig code and npm DuckDB dependency from @rip-lang/db
|
|
41
|
+
- Consolidated documentation (DEBUGGING.md + INTERNALS.md → README.md)
|
|
42
|
+
- Dynamic platform detection for DuckDB UI headers
|
|
43
|
+
- Support both `--port=N` and `--port N` argument formats
|
|
44
|
+
- Updated all package dependencies to ^2.9.0
|
|
45
|
+
- Removed bun.lock from git tracking
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
10
49
|
## [2.7.2] - 2026-02-03
|
|
11
50
|
|
|
12
51
|
### Clean ES Module REPL
|
package/README.md
CHANGED
|
@@ -72,6 +72,16 @@ class Dog extends Animal
|
|
|
72
72
|
dog = Dog.new("Buddy") # Ruby-style constructor
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
+
### String Interpolation
|
|
76
|
+
|
|
77
|
+
```coffee
|
|
78
|
+
"Hello, #{name}!" # CoffeeScript-style
|
|
79
|
+
"Hello, ${name}!" # JavaScript-style
|
|
80
|
+
"#{a} + #{b} = #{a + b}" # Expressions work in both
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Both `#{}` and `${}` compile to JavaScript template literals. Use whichever you prefer.
|
|
84
|
+
|
|
75
85
|
### Destructuring & Comprehensions
|
|
76
86
|
|
|
77
87
|
```coffee
|
|
@@ -136,15 +146,35 @@ State, computed values, and effects as language operators:
|
|
|
136
146
|
|
|
137
147
|
### Heredoc & Heregex
|
|
138
148
|
|
|
139
|
-
**Heredoc** — The closing `'''` position
|
|
149
|
+
**Heredoc** — The closing `'''` or `"""` position defines the left margin. All content is dedented relative to the column where the closing delimiter sits:
|
|
140
150
|
|
|
141
151
|
```coffee
|
|
152
|
+
html = '''
|
|
153
|
+
<div>
|
|
154
|
+
<p>Hello</p>
|
|
155
|
+
</div>
|
|
156
|
+
'''
|
|
157
|
+
# Closing ''' is at column 4 (same as content) → no leading whitespace
|
|
158
|
+
# Result: "<div>\n <p>Hello</p>\n</div>"
|
|
159
|
+
|
|
142
160
|
html = '''
|
|
143
161
|
<div>
|
|
144
162
|
<p>Hello</p>
|
|
145
163
|
</div>
|
|
146
164
|
'''
|
|
147
|
-
#
|
|
165
|
+
# Closing ''' is at column 2 (2 less than content) → 2 spaces of leading whitespace
|
|
166
|
+
# Result: " <div>\n <p>Hello</p>\n </div>"
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Use `"""` for interpolation, `'''` for literal strings. The same margin rule applies to both:
|
|
170
|
+
|
|
171
|
+
```coffee
|
|
172
|
+
name = "World"
|
|
173
|
+
msg = """
|
|
174
|
+
Hello, #{name}!
|
|
175
|
+
Welcome to Rip.
|
|
176
|
+
"""
|
|
177
|
+
# Result: "Hello, World!\nWelcome to Rip."
|
|
148
178
|
```
|
|
149
179
|
|
|
150
180
|
**Heregex** — Extended regex with comments and whitespace:
|
package/docs/BROWSER.md
CHANGED
|
@@ -166,7 +166,7 @@ Variables:
|
|
|
166
166
|
Split-pane editor showing real-time compilation:
|
|
167
167
|
|
|
168
168
|
**Left Pane (Rip):**
|
|
169
|
-
```
|
|
169
|
+
```coffee
|
|
170
170
|
def fibonacci(n)
|
|
171
171
|
if n <= 1
|
|
172
172
|
n
|
|
@@ -335,7 +335,7 @@ Build tools, playgrounds, or educational apps:
|
|
|
335
335
|
### All Rip Features Available
|
|
336
336
|
|
|
337
337
|
**✅ Modern Syntax:**
|
|
338
|
-
```
|
|
338
|
+
```coffee
|
|
339
339
|
# Destructuring
|
|
340
340
|
{name, age} = person
|
|
341
341
|
[first, ...rest] = array
|
|
@@ -357,7 +357,7 @@ pattern = ///
|
|
|
357
357
|
```
|
|
358
358
|
|
|
359
359
|
**✅ Ruby-Style Regex:**
|
|
360
|
-
```
|
|
360
|
+
```coffee
|
|
361
361
|
# Match operator
|
|
362
362
|
email =~ /(.+)@(.+)/
|
|
363
363
|
domain = _[2]
|
|
@@ -367,7 +367,7 @@ zip = "12345-6789"[/^(\d{5})/, 1] # "12345"
|
|
|
367
367
|
```
|
|
368
368
|
|
|
369
369
|
**✅ Functions:**
|
|
370
|
-
```
|
|
370
|
+
```coffee
|
|
371
371
|
# Three styles
|
|
372
372
|
def add(a, b) # Hoisted
|
|
373
373
|
a + b
|
|
@@ -380,7 +380,7 @@ divide = (a, b) => # Bound this
|
|
|
380
380
|
```
|
|
381
381
|
|
|
382
382
|
**✅ Classes:**
|
|
383
|
-
```
|
|
383
|
+
```coffee
|
|
384
384
|
class Person
|
|
385
385
|
constructor: (@name, @age) ->
|
|
386
386
|
|
|
@@ -389,7 +389,7 @@ class Person
|
|
|
389
389
|
```
|
|
390
390
|
|
|
391
391
|
**✅ Async/Await:**
|
|
392
|
-
```
|
|
392
|
+
```coffee
|
|
393
393
|
# Auto-detected!
|
|
394
394
|
fetchData = ->
|
|
395
395
|
response = await fetch(url)
|
|
@@ -402,7 +402,7 @@ getData = ->
|
|
|
402
402
|
```
|
|
403
403
|
|
|
404
404
|
**✅ String Interpolation:**
|
|
405
|
-
```
|
|
405
|
+
```coffee
|
|
406
406
|
name = "World"
|
|
407
407
|
message = "Hello, #{name}!"
|
|
408
408
|
|
|
@@ -576,7 +576,7 @@ Real-time data processing with clean syntax:
|
|
|
576
576
|
|
|
577
577
|
The `toSearchable()` helper includes injection protection:
|
|
578
578
|
|
|
579
|
-
```
|
|
579
|
+
```coffee
|
|
580
580
|
# Safe by default - rejects newlines
|
|
581
581
|
userInput =~ /^[a-z]+$/ # Returns null if input has \n
|
|
582
582
|
|
package/docs/GUIDE.md
CHANGED
|
@@ -483,7 +483,7 @@ Rip extends CoffeeScript with two powerful regex features inspired by Ruby: the
|
|
|
483
483
|
|
|
484
484
|
### Syntax
|
|
485
485
|
|
|
486
|
-
```
|
|
486
|
+
```coffee
|
|
487
487
|
text =~ /pattern/
|
|
488
488
|
```
|
|
489
489
|
|
|
@@ -496,14 +496,14 @@ text =~ /pattern/
|
|
|
496
496
|
### Examples
|
|
497
497
|
|
|
498
498
|
**Basic matching:**
|
|
499
|
-
```
|
|
499
|
+
```coffee
|
|
500
500
|
text = "hello world"
|
|
501
501
|
if text =~ /world/
|
|
502
502
|
console.log("Found:", _[0]) # "world"
|
|
503
503
|
```
|
|
504
504
|
|
|
505
505
|
**Capture groups:**
|
|
506
|
-
```
|
|
506
|
+
```coffee
|
|
507
507
|
email = "user@example.com"
|
|
508
508
|
if email =~ /(.+)@(.+)/
|
|
509
509
|
username = _[1] # "user"
|
|
@@ -511,7 +511,7 @@ if email =~ /(.+)@(.+)/
|
|
|
511
511
|
```
|
|
512
512
|
|
|
513
513
|
**Phone number parsing:**
|
|
514
|
-
```
|
|
514
|
+
```coffee
|
|
515
515
|
phone = "2125551234"
|
|
516
516
|
if phone =~ /^([2-9]\d\d)([2-9]\d\d)(\d{4})$/
|
|
517
517
|
formatted = "(#{_[1]}) #{_[2]}-#{_[3]}"
|
|
@@ -522,7 +522,7 @@ if phone =~ /^([2-9]\d\d)([2-9]\d\d)(\d{4})$/
|
|
|
522
522
|
|
|
523
523
|
### Syntax
|
|
524
524
|
|
|
525
|
-
```
|
|
525
|
+
```coffee
|
|
526
526
|
value[/pattern/] # Returns full match (capture 0)
|
|
527
527
|
value[/pattern/, n] # Returns capture group n
|
|
528
528
|
```
|
|
@@ -530,17 +530,17 @@ value[/pattern/, n] # Returns capture group n
|
|
|
530
530
|
### Examples
|
|
531
531
|
|
|
532
532
|
**Simple match:**
|
|
533
|
-
```
|
|
533
|
+
```coffee
|
|
534
534
|
"steve"[/eve/] # Returns "eve"
|
|
535
535
|
```
|
|
536
536
|
|
|
537
537
|
**Capture group:**
|
|
538
|
-
```
|
|
538
|
+
```coffee
|
|
539
539
|
"steve"[/e(v)e/, 1] # Returns "v"
|
|
540
540
|
```
|
|
541
541
|
|
|
542
542
|
**Email domain:**
|
|
543
|
-
```
|
|
543
|
+
```coffee
|
|
544
544
|
domain = "user@example.com"[/@(.+)$/, 1]
|
|
545
545
|
# Returns: "example.com"
|
|
546
546
|
```
|
|
@@ -549,7 +549,7 @@ domain = "user@example.com"[/@(.+)$/, 1]
|
|
|
549
549
|
|
|
550
550
|
The real power comes from using both features together:
|
|
551
551
|
|
|
552
|
-
```
|
|
552
|
+
```coffee
|
|
553
553
|
# Parse, validate, and format in clean steps
|
|
554
554
|
email = "Admin@Company.COM"
|
|
555
555
|
if email =~ /^([^@]+)@([^@]+)$/i
|
|
@@ -562,7 +562,7 @@ if email =~ /^([^@]+)@([^@]+)$/i
|
|
|
562
562
|
|
|
563
563
|
One of the most powerful use cases is building validators:
|
|
564
564
|
|
|
565
|
-
```
|
|
565
|
+
```coffee
|
|
566
566
|
validators =
|
|
567
567
|
# Extract and validate in one expression
|
|
568
568
|
id: (v) -> v[/^([1-9]\d{0,19})$/] and parseInt(_[1])
|
|
@@ -589,7 +589,7 @@ validators =
|
|
|
589
589
|
|
|
590
590
|
Rip supports heregexes - extended regular expressions that allow whitespace and comments for readability:
|
|
591
591
|
|
|
592
|
-
```
|
|
592
|
+
```coffee
|
|
593
593
|
pattern = ///
|
|
594
594
|
^ \d+ # starts with digits
|
|
595
595
|
\s* # optional whitespace
|
|
@@ -607,7 +607,7 @@ pattern = ///
|
|
|
607
607
|
|
|
608
608
|
By default, **rejects strings with newlines**:
|
|
609
609
|
|
|
610
|
-
```
|
|
610
|
+
```coffee
|
|
611
611
|
# Safe - rejects malicious input
|
|
612
612
|
userInput = "test\nmalicious"
|
|
613
613
|
userInput =~ /^test$/ # Returns null! (newline detected)
|
|
@@ -632,5 +632,5 @@ text =~ /line2/m # Works! (/m flag allows newlines)
|
|
|
632
632
|
|
|
633
633
|
**See Also:**
|
|
634
634
|
- [INTERNALS.md](INTERNALS.md) - Compiler and parser details
|
|
635
|
-
- [
|
|
635
|
+
- [RATIONALE.md](RATIONALE.md) - Why Rip exists
|
|
636
636
|
- [BROWSER.md](BROWSER.md) - Browser usage and REPL guide
|
package/docs/INTERNALS.md
CHANGED
|
@@ -43,7 +43,7 @@ Source Code → CoffeeScript Lexer → Solar Parser → S-Expressions → Codege
|
|
|
43
43
|
|
|
44
44
|
## Example Flow
|
|
45
45
|
|
|
46
|
-
```
|
|
46
|
+
```coffee
|
|
47
47
|
# Input
|
|
48
48
|
x = 42
|
|
49
49
|
|
|
@@ -320,7 +320,7 @@ generate(sexpr, context = 'statement') {
|
|
|
320
320
|
|
|
321
321
|
**Example: Comprehensions**
|
|
322
322
|
|
|
323
|
-
```
|
|
323
|
+
```coffee
|
|
324
324
|
# Statement context (result discarded) → Plain loop
|
|
325
325
|
console.log x for x in arr
|
|
326
326
|
# → for (const x of arr) { console.log(x); }
|
|
@@ -364,7 +364,7 @@ fn = function() {
|
|
|
364
364
|
|
|
365
365
|
**Functions automatically become async or generators:**
|
|
366
366
|
|
|
367
|
-
```
|
|
367
|
+
```coffee
|
|
368
368
|
# Contains await → becomes async function
|
|
369
369
|
getData = ->
|
|
370
370
|
result = await fetch(url)
|
|
@@ -405,7 +405,7 @@ fetchAll = ->
|
|
|
405
405
|
| `a ??= 10` | ES6 | `a ??= 10` |
|
|
406
406
|
|
|
407
407
|
**Mix and match:**
|
|
408
|
-
```
|
|
408
|
+
```coffee
|
|
409
409
|
obj?.arr?[0] # ES6 + CoffeeScript together!
|
|
410
410
|
```
|
|
411
411
|
|
|
@@ -413,7 +413,7 @@ obj?.arr?[0] # ES6 + CoffeeScript together!
|
|
|
413
413
|
|
|
414
414
|
**Traditional for loops instead of wasteful IIFEs:**
|
|
415
415
|
|
|
416
|
-
```
|
|
416
|
+
```coffee
|
|
417
417
|
# Optimized to traditional loop
|
|
418
418
|
for i in [1...100]
|
|
419
419
|
process(i)
|
|
@@ -424,7 +424,7 @@ for i in [1...100]
|
|
|
424
424
|
```
|
|
425
425
|
|
|
426
426
|
**Reverse iteration support:**
|
|
427
|
-
```
|
|
427
|
+
```coffee
|
|
428
428
|
for i in [10..1] by -1
|
|
429
429
|
process(i)
|
|
430
430
|
# → for (let i = 10; i >= 1; i--) { process(i); }
|
|
@@ -472,7 +472,7 @@ Comprehensions can act as **data builders** or **control loops**. Rip distinguis
|
|
|
472
472
|
- **Benefit:** Automatic optimization - no wasteful array building!
|
|
473
473
|
|
|
474
474
|
**Example of improvement:**
|
|
475
|
-
```
|
|
475
|
+
```coffee
|
|
476
476
|
fn = ->
|
|
477
477
|
process x for x in arr # ← Rip: plain loop! CS: IIFE (wasteful)
|
|
478
478
|
doMore()
|
|
@@ -540,7 +540,7 @@ for (const x of arr) {
|
|
|
540
540
|
|
|
541
541
|
### Own + Guard + Value Variable (Critical!)
|
|
542
542
|
|
|
543
|
-
```
|
|
543
|
+
```coffee
|
|
544
544
|
for own k, v of obj when v > 5
|
|
545
545
|
process(k, v)
|
|
546
546
|
```
|
|
@@ -562,13 +562,13 @@ for (const k in obj) {
|
|
|
562
562
|
### Async Comprehensions
|
|
563
563
|
|
|
564
564
|
**Sequential (await in body):**
|
|
565
|
-
```
|
|
565
|
+
```coffee
|
|
566
566
|
# IIFE is async, awaits happen serially inside loop
|
|
567
567
|
results = (await fetchData(url) for url in urls)
|
|
568
568
|
```
|
|
569
569
|
|
|
570
570
|
**Parallel (recommended for I/O):**
|
|
571
|
-
```
|
|
571
|
+
```coffee
|
|
572
572
|
# Build array of promises, then await all in parallel
|
|
573
573
|
results = await Promise.all (fetchData(url) for url in urls)
|
|
574
574
|
```
|
|
@@ -853,5 +853,5 @@ Solar's s-expression mode is the **secret sauce** that makes Rip practical:
|
|
|
853
853
|
|
|
854
854
|
**See Also:**
|
|
855
855
|
- [GUIDE.md](GUIDE.md) - Language features and syntax
|
|
856
|
-
- [
|
|
856
|
+
- [RATIONALE.md](RATIONALE.md) - Design decisions and rationale
|
|
857
857
|
- [BROWSER.md](BROWSER.md) - Browser usage and REPL guide
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
<p><img src="rip.svg" alt="Rip Logo" width="100"></p>
|
|
2
|
+
|
|
3
|
+
# Why Rip Exists
|
|
4
|
+
|
|
5
|
+
> Philosophy, design decisions, and the case for a modern CoffeeScript successor.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## The Short Version
|
|
10
|
+
|
|
11
|
+
Rip exists because:
|
|
12
|
+
|
|
13
|
+
1. **Simplicity scales** — S-expressions make compilers 50% smaller and 10x easier to maintain
|
|
14
|
+
2. **Zero dependencies** — True autonomy from the npm ecosystem
|
|
15
|
+
3. **Modern output** — ES2022 everywhere, no legacy baggage
|
|
16
|
+
4. **Unique features** — 10+ innovations CoffeeScript never had
|
|
17
|
+
5. **Reactivity as operators** — `:=`, `~=`, `~>` are language syntax, not library imports
|
|
18
|
+
6. **Self-hosting** — Rip compiles itself, including its own parser generator
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 1. Why S-Expressions
|
|
23
|
+
|
|
24
|
+
Most compilers use complex AST node classes. Rip uses **simple arrays**:
|
|
25
|
+
|
|
26
|
+
```javascript
|
|
27
|
+
// Traditional AST (CoffeeScript, TypeScript, Babel)
|
|
28
|
+
class BinaryOp {
|
|
29
|
+
constructor(op, left, right) { ... }
|
|
30
|
+
compile() { /* 50+ lines */ }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Rip's S-Expression
|
|
34
|
+
["+", left, right] // That's it!
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Result:** CoffeeScript's compiler is 17,760 LOC. Rip's is ~11,000 LOC — smaller, yet includes a complete reactive runtime.
|
|
38
|
+
|
|
39
|
+
### The Fundamental Rule
|
|
40
|
+
|
|
41
|
+
> **Transform the IR (s-expressions), not the output (strings)**
|
|
42
|
+
|
|
43
|
+
This single principle eliminates entire categories of bugs. When your IR is simple data (arrays), transformations are trivial and debuggable:
|
|
44
|
+
|
|
45
|
+
```javascript
|
|
46
|
+
// Debugging: inspect the data directly
|
|
47
|
+
console.log(sexpr);
|
|
48
|
+
// ["comprehension", ["*", "x", 2], [["for-in", ["x"], ["array", 1, 2, 3]]], []]
|
|
49
|
+
// Clear structure, 2-3 minutes to debug
|
|
50
|
+
|
|
51
|
+
// vs. string manipulation
|
|
52
|
+
console.log(code);
|
|
53
|
+
// "(() => {\n const result = [];\n for (const x of arr) {\n..."
|
|
54
|
+
// Blob of text, 20-30 minutes to debug
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Lisp got this right 60 years ago.** Code is data, data is code.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 2. Rip vs CoffeeScript
|
|
62
|
+
|
|
63
|
+
| Metric | CoffeeScript | Rip |
|
|
64
|
+
|--------|--------------|-----|
|
|
65
|
+
| **Feature Parity** | Baseline | ~99% |
|
|
66
|
+
| **Unique Features** | 0 | 10+ innovations |
|
|
67
|
+
| **Dependencies** | Multiple | **ZERO** |
|
|
68
|
+
| **Self-Hosting** | No | **YES** |
|
|
69
|
+
| **Total LOC** | 17,760 | ~11,000 |
|
|
70
|
+
| **Output** | ES6 (2017) | ES2022 |
|
|
71
|
+
| **Reactivity** | None | Built-in |
|
|
72
|
+
|
|
73
|
+
### What Rip Adds
|
|
74
|
+
|
|
75
|
+
1. **Reactivity as operators** — `:=` state, `~=` computed, `~>` effects
|
|
76
|
+
2. **Ternary operator** — `x ? a : b` (freed by using `??` for nullish)
|
|
77
|
+
3. **Dammit operator** — `fetchData!` calls AND awaits
|
|
78
|
+
4. **Ruby-style regex** — `str =~ /pattern/` with captures in `_`
|
|
79
|
+
5. **Dual optional syntax** — Both CoffeeScript soak AND ES6 optional chaining
|
|
80
|
+
6. **Void functions** — `def process!` suppresses implicit returns
|
|
81
|
+
7. **Smart comprehensions** — Context-aware (loop vs. array building)
|
|
82
|
+
8. **`__DATA__` marker** — Ruby-style inline data sections
|
|
83
|
+
9. **Heregex** — Extended regex with comments and whitespace
|
|
84
|
+
10. **Zero dependencies** — Everything included, even the parser generator
|
|
85
|
+
|
|
86
|
+
### The Complete Feature Table
|
|
87
|
+
|
|
88
|
+
| Feature | CoffeeScript | Rip | Winner |
|
|
89
|
+
|---------|-------------|------|--------|
|
|
90
|
+
| Optional operators | 4 soak | 10 (5 soak + 5 ES6) | Rip |
|
|
91
|
+
| Ternary | No | Yes | Rip |
|
|
92
|
+
| Regex features | Basic | Ruby-style (`=~`, indexing) | Rip |
|
|
93
|
+
| Async shorthand | No | Dammit operator (`!`) | Rip |
|
|
94
|
+
| Void functions | No | `def fn!` | Rip |
|
|
95
|
+
| Reactivity | None | `:=`, `~=`, `~>` | Rip |
|
|
96
|
+
| Comprehension optimization | Always IIFE | Context-aware | Rip |
|
|
97
|
+
| Modules | CommonJS | ES6 | Rip |
|
|
98
|
+
| Classes | ES5 | ES6 | Rip |
|
|
99
|
+
| Dependencies | Multiple | **ZERO** | Rip |
|
|
100
|
+
| Parser generator | External (Jison) | **Built-in (solar.rip)** | Rip |
|
|
101
|
+
| Self-hosting | No | **Yes** | Rip |
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## 3. The Case Against CoffeeScript (And Why Rip Is Different)
|
|
106
|
+
|
|
107
|
+
The strongest argument against CoffeeScript in 2026:
|
|
108
|
+
|
|
109
|
+
- **Ecosystem abandoned** — Hiring, tooling, community all gone
|
|
110
|
+
- **TypeScript won** — Type safety became the standard
|
|
111
|
+
- **JavaScript absorbed it** — `?.`, `??`, `=>`, classes, destructuring all native now
|
|
112
|
+
- **Tooling rotted** — IDE support deprecated, build plugins unmaintained
|
|
113
|
+
|
|
114
|
+
**These are valid criticisms.** Rip addresses every one:
|
|
115
|
+
|
|
116
|
+
| Criticism | CoffeeScript's Problem | Rip's Answer |
|
|
117
|
+
|-----------|----------------------|--------------|
|
|
118
|
+
| No ecosystem | Dead community | Zero dependencies — doesn't need one |
|
|
119
|
+
| No types | Can't integrate with TS tooling | ES2022 output works with any JS toolchain |
|
|
120
|
+
| Tooling rot | Plugins unmaintained | Self-contained — nothing to maintain |
|
|
121
|
+
| No innovation | Frozen since 2017 | 10+ unique features, active development |
|
|
122
|
+
| No corporate backing | Abandoned | Self-hosting — depends on nothing |
|
|
123
|
+
|
|
124
|
+
**The key insight:** CoffeeScript failed because it depended on an ecosystem that moved on. Rip succeeds by depending on nothing.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## 4. Reactivity as a Language Feature
|
|
129
|
+
|
|
130
|
+
This is Rip's signature innovation. While React, Vue, and Solid provide reactivity through library imports, Rip provides it as **language operators**:
|
|
131
|
+
|
|
132
|
+
```coffee
|
|
133
|
+
count := 0 # State — reactive container
|
|
134
|
+
doubled ~= count * 2 # Computed — auto-updates when count changes
|
|
135
|
+
~> console.log count # Effect — runs when dependencies change
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Compare to React:
|
|
139
|
+
```javascript
|
|
140
|
+
import { useState, useMemo, useEffect } from 'react';
|
|
141
|
+
const [count, setCount] = useState(0);
|
|
142
|
+
const doubled = useMemo(() => count * 2, [count]);
|
|
143
|
+
useEffect(() => { console.log(count); }, [count]);
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**No imports. No hooks. No dependency arrays. Just operators.**
|
|
147
|
+
|
|
148
|
+
The reactive runtime is ~200 lines, embedded in the compiler, and only included when reactive operators are used.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## 5. Design Principles
|
|
153
|
+
|
|
154
|
+
### Simplicity Scales
|
|
155
|
+
|
|
156
|
+
Simple IR (s-expressions), clear pipeline (lex → parse → generate), minimal code, comprehensive tests. Every design decision optimizes for **long-term maintainability**, not short-term cleverness.
|
|
157
|
+
|
|
158
|
+
### Zero Dependencies Is a Feature
|
|
159
|
+
|
|
160
|
+
```json
|
|
161
|
+
{ "dependencies": {} }
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Everything included: compiler, parser generator, REPL, browser bundle, test framework. No supply chain attacks. No version conflicts. No `node_modules` bloat.
|
|
165
|
+
|
|
166
|
+
### Self-Hosting Proves Quality
|
|
167
|
+
|
|
168
|
+
Rip compiles its own parser generator (`solar.rip` → `parser.js`). If the compiler can compile itself, it works.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
**See Also:**
|
|
173
|
+
- [GUIDE.md](GUIDE.md) — Complete language reference
|
|
174
|
+
- [REACTIVITY.md](REACTIVITY.md) — Reactivity deep dive
|
|
175
|
+
- [INTERNALS.md](INTERNALS.md) — Compiler architecture
|
|
176
|
+
- [BROWSER.md](BROWSER.md) — Browser usage and REPL
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
*Version 2.9.0 — 1115/1115 tests passing — Zero dependencies — Self-hosting*
|
package/docs/REACTIVITY.md
CHANGED
|
@@ -51,7 +51,7 @@ Every reactive system reduces to these three concepts:
|
|
|
51
51
|
|
|
52
52
|
## Quick Example
|
|
53
53
|
|
|
54
|
-
```
|
|
54
|
+
```coffee
|
|
55
55
|
count := 0 # count has state 0
|
|
56
56
|
doubled ~= count * 2 # doubled always equals count * 2
|
|
57
57
|
logger ~> console.log count # reacts to count changes
|
|
@@ -71,7 +71,7 @@ increment() # Logs: 2
|
|
|
71
71
|
|
|
72
72
|
State creates a **reactive container** that tracks its readers and notifies them on change.
|
|
73
73
|
|
|
74
|
-
```
|
|
74
|
+
```coffee
|
|
75
75
|
count := 0 # count has state 0
|
|
76
76
|
count += 1 # Update triggers dependents
|
|
77
77
|
```
|
|
@@ -91,7 +91,7 @@ count.value += 1;
|
|
|
91
91
|
|
|
92
92
|
Computed creates a **computed value** that automatically updates when dependencies change.
|
|
93
93
|
|
|
94
|
-
```
|
|
94
|
+
```coffee
|
|
95
95
|
count := 0
|
|
96
96
|
doubled ~= count * 2 # doubled always equals count * 2
|
|
97
97
|
```
|
|
@@ -105,7 +105,7 @@ doubled ~= count * 2 # doubled always equals count * 2
|
|
|
105
105
|
|
|
106
106
|
The effect operator runs **side effects** when dependencies change. Dependencies are auto-tracked from reactive values read in the body.
|
|
107
107
|
|
|
108
|
-
```
|
|
108
|
+
```coffee
|
|
109
109
|
~> document.title = "Count: #{count}"
|
|
110
110
|
```
|
|
111
111
|
|
|
@@ -115,7 +115,7 @@ The effect operator runs **side effects** when dependencies change. Dependencies
|
|
|
115
115
|
- **Controllable** — optionally assign to a variable to control the effect
|
|
116
116
|
|
|
117
117
|
**Syntax:**
|
|
118
|
-
```
|
|
118
|
+
```coffee
|
|
119
119
|
# Fire and forget (no assignment)
|
|
120
120
|
~> console.log count
|
|
121
121
|
|