rip-lang 2.9.1 → 3.0.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/docs/RATIONALE.md DELETED
@@ -1,180 +0,0 @@
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*
@@ -1,33 +0,0 @@
1
- # Rip Examples
2
-
3
- Focused examples demonstrating Rip's key features. Each file is self-contained
4
- and can be compiled with `rip -c <file>` to see the generated JavaScript.
5
-
6
- ## Core Language
7
-
8
- | Example | Demonstrates |
9
- |---------|-------------|
10
- | [fibonacci.rip](fibonacci.rip) | Functions, implicit returns, recursion |
11
- | [arrows.rip](arrows.rip) | Arrow functions, callbacks, implicit object returns |
12
- | [switch.rip](switch.rip) | Switch/when patterns (value and condition based) |
13
- | [ternary.rip](ternary.rip) | JS-style ternary operator (`? :`) |
14
- | [ranges.rip](ranges.rip) | Inclusive (`..`) and exclusive (`...`) ranges |
15
-
16
- ## Advanced Features
17
-
18
- | Example | Demonstrates |
19
- |---------|-------------|
20
- | [reactivity.rip](reactivity.rip) | State (`:=`), computed (`~=`), effects (`~>`) |
21
- | [async-await.rip](async-await.rip) | Dammit operator (`!`), auto-async detection |
22
- | [existential.rip](existential.rip) | Null coalescing (`??`), optional chaining (`?.`) |
23
- | [module.rip](module.rip) | Imports, exports, module patterns |
24
-
25
- ## Quick Start
26
-
27
- ```bash
28
- # See what Rip generates
29
- rip -c docs/examples/fibonacci.rip
30
-
31
- # Run an example
32
- rip docs/examples/reactivity.rip
33
- ```
@@ -1,74 +0,0 @@
1
- # Arrow Function Examples
2
-
3
- # =============================================================================
4
- # Basic Arrow Functions (=> binds 'this', -> does not)
5
- # =============================================================================
6
-
7
- # Simple arrows
8
- add = (a, b) => a + b
9
- square = (x) => x * x
10
- greet = (name) => "Hello, #{name}!" # CoffeeScript-style interpolation
11
- farewell = (name) => "Goodbye, ${name}!" # JavaScript-style also works
12
-
13
- # Arrow functions in objects (methods!)
14
- math = {
15
- add: (a, b) => a + b
16
- subtract: (a, b) => a - b
17
- multiply: (a, b) => a * b
18
- divide: (a, b) => a / b
19
- }
20
-
21
- # Arrow functions as callbacks
22
- numbers = [1, 2, 3, 4, 5]
23
- doubled = numbers.map((x) => x * 2)
24
- evens = numbers.filter((x) => x % 2 == 0)
25
- sum = numbers.reduce((acc, x) => acc + x, 0)
26
-
27
- # Arrow functions with object methods
28
- users = [
29
- {name: "Alice", age: 30}
30
- {name: "Bob", age: 25}
31
- ]
32
-
33
- names = users.map((u) => u.name)
34
- adults = users.filter((u) => u.age >= 18)
35
-
36
- # Composing arrows
37
- pipeline = {
38
- transform: (data) => data.map((x) => x * 2)
39
- filter: (data) => data.filter((x) => x > 5)
40
- process: (data) => data.reduce((sum, x) => sum + x, 0)
41
- }
42
-
43
- # =============================================================================
44
- # Implicit Object Returns (CoffeeScript-style elegance!)
45
- # =============================================================================
46
-
47
- # Simple implicit object (no braces needed!)
48
- makePoint = (x, y) =>
49
- x: x
50
- y: y
51
-
52
- # With shorthand (use explicit braces)
53
- makePoint2 = (x, y) => {x, y}
54
-
55
- # Data transformation
56
- enrichUser = (user) =>
57
- id: user.id
58
- name: user.name
59
- email: user.email
60
- active: true
61
- joined: Date.now()
62
-
63
- # Simple API methods
64
- api = {
65
- getUser: (id) =>
66
- id: id
67
- name: "User #{id}"
68
- timestamp: Date.now()
69
-
70
- createResponse: (data, status) =>
71
- data: data
72
- status: status
73
- success: status == 200
74
- }
@@ -1,59 +0,0 @@
1
- # Async/Await with the "Dammit Operator" (!)
2
- # Clean, visual async syntax
3
-
4
- # Simple async function
5
- def loadUser(id)
6
- user = Db.findOne!(id)
7
- user
8
-
9
- # Multiple awaits (auto-detected as async!)
10
- def getUserProfile(id)
11
- user = Db.getUser!(id)
12
- posts = Db.getPosts!(user.id)
13
- friends = Db.getFriends!(user.id)
14
-
15
- {user, posts, friends}
16
-
17
- # No arguments - auto-calls!
18
- def refreshCache()
19
- data = fetchLatest!
20
- cache = getCache!
21
- cache.set!(data)
22
- data
23
-
24
- # Await in conditionals
25
- def checkStatus(id)
26
- user = Db.getUser!(id)
27
-
28
- if user.active
29
- status = Api.getStatus!(user.id)
30
- {user, status, active: true}
31
- else
32
- {user, active: false}
33
-
34
- # Arrow functions with await
35
- fetchAndProcess = (id) => Api.fetch!(id).then!(processData)
36
-
37
- # Await in object methods
38
- api = {
39
- loadData: (key) =>
40
- data: Cache.get!(key)
41
- timestamp: Date.now()
42
-
43
- saveData: (key, value) =>
44
- result = Cache.set!(key, value)
45
- success: result.ok
46
- }
47
-
48
- # Complex async flow
49
- def processUserData(userId)
50
- user = Db.getUser!(userId)
51
- settings = Db.getSettings!(userId)
52
- activity = Api.getActivity!(userId)
53
-
54
- {
55
- user
56
- settings
57
- activity
58
- fetchedAt: Date.now()
59
- }
@@ -1,86 +0,0 @@
1
- # Existential and Logical Assignment Operators
2
- # Demonstrates the difference between nullish and falsy checks
3
-
4
- # Binary Nullish Coalescing (??)
5
- # Only replaces null/undefined
6
- def getPort(config)
7
- config.port ?? 8080
8
-
9
- # Preserves 0, false, empty string
10
- port1 = getPort({port: 0}) # → 0 (not replaced!)
11
- port2 = getPort({port: null}) # → 8080 (replaced)
12
- port3 = getPort({}) # → 8080 (undefined)
13
-
14
- # Binary Logical OR (||)
15
- # Replaces ALL falsy values
16
- def getPortFalsy(config)
17
- config.port || 8080
18
-
19
- portA = getPortFalsy({port: 0}) # → 8080 (0 is falsy)
20
- portB = getPortFalsy({port: null}) # → 8080 (null is falsy)
21
-
22
- # Existential Assignment (?=)
23
- # Assign only if null/undefined
24
- def initializeConfig()
25
- config = {}
26
- config.port ?= 8080 # Sets to 8080 (undefined)
27
- config.debug ?= false # Sets to false (undefined)
28
- config.timeout ?= 0 # Sets to 0 (undefined)
29
-
30
- # These won't change existing values
31
- config.port ?= 9000 # Stays 8080
32
- config.debug ?= true # Stays false (preserves false!)
33
- config.timeout ?= 5000 # Stays 0 (preserves 0!)
34
-
35
- config
36
-
37
- # Logical OR Assignment (||=)
38
- # Assign if falsy
39
- def setDefaults(options)
40
- options.name ||= "guest" # Replaces empty string
41
- options.count ||= 10 # Replaces 0
42
- options.enabled ||= true # Replaces false
43
- options
44
-
45
- # Logical AND Assignment (&&=)
46
- # Update only if truthy
47
- def processIfValid(data)
48
- data.value &&= data.value * 2 # Only process if exists
49
- data
50
-
51
- # Real-world example: User preferences
52
- def getUserPreferences(user)
53
- prefs = user?.preferences ?? {}
54
-
55
- # Set defaults for missing values (existential)
56
- prefs.theme ?= "dark"
57
- prefs.fontSize ?= 14
58
- prefs.notifications ?= true
59
-
60
- # But replace falsy user inputs (logical)
61
- prefs.username ||= "anonymous"
62
-
63
- prefs
64
-
65
- # Chained nullish coalescing
66
- def getConfigValue(primary, secondary, tertiary)
67
- primary ?? secondary ?? tertiary ?? "default"
68
-
69
- # Comparison table
70
- def demonstrateDifference()
71
- testValues = [null, undefined, false, 0, "", "text", 42]
72
-
73
- # For each value, show behavior of ?? vs ||
74
- results = []
75
-
76
- testValues.forEach((val) => {
77
- nullish = val ?? "default"
78
- logical = val || "default"
79
- results.push({
80
- value: val
81
- nullish: nullish
82
- logical: logical
83
- })
84
- })
85
-
86
- results
@@ -1,12 +0,0 @@
1
- # Fibonacci example in Rip
2
- # Whitespace-sensitive s-expression syntax
3
- # No let/const needed, no return needed!
4
-
5
- def fibonacci(n)
6
- if n <= 1
7
- n
8
- else
9
- fibonacci(n - 1) + fibonacci(n - 2)
10
-
11
- # Test it
12
- result = fibonacci(10)
@@ -1,48 +0,0 @@
1
- # Modules — Imports, Exports, and Module Patterns
2
-
3
- # =============================================================================
4
- # Importing
5
- # =============================================================================
6
-
7
- # Named imports
8
- import { readFileSync, writeFileSync } from "fs"
9
- import { join, resolve } from "path"
10
-
11
- # Namespace import
12
- import * as os from "os"
13
-
14
- # Default import
15
- # import express from "express"
16
-
17
- # =============================================================================
18
- # Exporting Functions
19
- # =============================================================================
20
-
21
- # Named function export
22
- export def greet(name)
23
- "Hello, #{name}!"
24
-
25
- # Arrow function export
26
- export double = (x) -> x * 2
27
- export format = (obj) -> JSON.stringify(obj, null, 2)
28
-
29
- # =============================================================================
30
- # Exporting Values
31
- # =============================================================================
32
-
33
- export config =
34
- timeout: 5000
35
- retries: 3
36
- verbose: false
37
-
38
- export AUTHOR =! "Rip" # const export
39
-
40
- # =============================================================================
41
- # Default Export
42
- # =============================================================================
43
-
44
- export default
45
- greet: greet
46
- double: double
47
- format: format
48
- config: config
@@ -1,45 +0,0 @@
1
- # Range Examples
2
- # Smart compilation: small ranges = inline, large ranges = IIFE
3
-
4
- # Inclusive ranges (..)
5
- def demoInclusive()
6
- nums = 1..5
7
- console.log("1..5 =", nums) # [1, 2, 3, 4, 5]
8
-
9
- # Exclusive ranges (...)
10
- def demoExclusive()
11
- nums = 1...5
12
- console.log("1...5 =", nums) # [1, 2, 3, 4]
13
-
14
- # Descending ranges
15
- def demoDescending()
16
- nums = 10..1
17
- console.log("10..1 =", nums.slice(0, 5)) # [10, 9, 8, 7, 6, ...]
18
-
19
- # Large ranges (generates IIFE, not inline)
20
- def demoLarge()
21
- nums = 1..100
22
- console.log("1..100 length =", nums.length) # 100
23
- console.log("First 5:", nums.slice(0, 5)) # [1, 2, 3, 4, 5]
24
-
25
- # Dynamic ranges (runtime values)
26
- def rangeBetween(start, end)
27
- start..end
28
-
29
- # Using ranges
30
- def sumRange(n)
31
- nums = 1..n
32
- sum = 0
33
- nums.forEach((x) => sum = sum + x)
34
- sum
35
-
36
- # Range in array
37
- def mixedArray()
38
- arr = [0, 1..3, 10, 20..22]
39
- console.log("Mixed:", arr) # [0, [1,2,3], 10, [20,21,22]]
40
-
41
- # Practical: Generate test data
42
- def generateIds(count)
43
- ids = 1..count
44
- ids.map((id) => id)
45
-
@@ -1,48 +0,0 @@
1
- # Reactivity — Rip's signature feature
2
- # Language-level operators for reactive state, computed values, and effects.
3
- # No imports. No hooks. No dependency arrays. Just operators.
4
-
5
- # =============================================================================
6
- # State (:= creates reactive state)
7
- # =============================================================================
8
-
9
- count := 0 # Reactive state variable
10
- name := "Alice" # Works with any type
11
- items := [1, 2, 3] # Arrays too
12
-
13
- # =============================================================================
14
- # Computed (~= auto-updates when dependencies change)
15
- # =============================================================================
16
-
17
- doubled ~= count * 2 # Always equals count * 2
18
- greeting ~= "Hello, #{name}!" # Recomputes when name changes
19
- total ~= items.reduce((a, b) -> a + b, 0)
20
-
21
- # =============================================================================
22
- # Effects (~> runs automatically when dependencies change)
23
- # =============================================================================
24
-
25
- # Anonymous effect (runs whenever count changes)
26
- ~> console.log "Count is now: #{count}"
27
-
28
- # Named effect (can be controlled/disposed)
29
- logger ~> console.log "#{name} has #{count} items"
30
-
31
- # =============================================================================
32
- # Const (=! for values that never change)
33
- # =============================================================================
34
-
35
- MAX_RETRIES =! 10 # const — "equals, dammit!"
36
- API_URL =! "https://api.example.com"
37
-
38
- # =============================================================================
39
- # How it works
40
- # =============================================================================
41
-
42
- # The operators compile to a tiny reactive runtime:
43
- # count := 0 → const count = __state(0)
44
- # doubled ~= count → const doubled = __computed(() => count * 2)
45
- # ~> console.log → __effect(() => console.log(...))
46
- #
47
- # The runtime is embedded in the compiler (~100 lines) and only
48
- # included in the output when reactive operators are used.
@@ -1,50 +0,0 @@
1
- # Switch/When/Else Examples
2
- # Two modes: value-based and condition-based
3
-
4
- # Value-based switch (matches against a value)
5
- def getDayType(day)
6
- switch day
7
- when "Mon", "Tue", "Wed", "Thu", "Fri" then "weekday"
8
- when "Sat", "Sun" then "weekend"
9
- else "unknown"
10
-
11
- # Condition-based switch (evaluates conditions)
12
- def getGrade(score)
13
- switch
14
- when score < 60 then "F"
15
- when score < 70 then "D"
16
- when score < 80 then "C"
17
- when score < 90 then "B"
18
- else "A"
19
-
20
- # With block syntax
21
- def processDay(day)
22
- switch day
23
- when "Mon"
24
- console.log("Start of week")
25
- "work mode"
26
- when "Fri"
27
- console.log("Almost weekend!")
28
- "party mode"
29
- when "Sat", "Sun"
30
- console.log("Weekend!")
31
- "relax mode"
32
- else
33
- "default mode"
34
-
35
- # Switch as expression (gets wrapped in IIFE)
36
- def categorize(value)
37
- category = switch value
38
- when 1, 2, 3 then "low"
39
- when 4, 5, 6 then "medium"
40
- when 7, 8, 9 then "high"
41
- else "out of range"
42
- category
43
-
44
- # Condition-based for complex logic
45
- def classify(x, y)
46
- switch
47
- when x > 100 && y > 100 then "both large"
48
- when x > 100 || y > 100 then "one large"
49
- when x > 0 && y > 0 then "both positive"
50
- else "other"
@@ -1,36 +0,0 @@
1
- # Ternary Operator Examples
2
- # Both ? : and if/then/else syntaxes supported!
3
-
4
- # Basic ternary
5
- def abs(x)
6
- result = x < 0 ? (0 - x) : x
7
- result
8
-
9
- # Nested ternary for classification
10
- def classify(n)
11
- n > 0 ? "positive" : n < 0 ? "negative" : "zero"
12
-
13
- # Ternary in expressions
14
- def max(a, b)
15
- a > b ? a : b
16
-
17
- # Ternary with objects
18
- def makePoint(x, y)
19
- x > 0 && y > 0 ? {x: x, y: y, quadrant: 1} : {x: x, y: y, quadrant: 0}
20
-
21
- # CoffeeScript-style inline if/then/else
22
- def greeting(isMorning)
23
- if isMorning then "Good morning!" else "Good evening!"
24
-
25
- def compare(a, b)
26
- if a > b then "greater" else if a < b then "less" else "equal"
27
-
28
- # Real-world example: validation
29
- def validateAge(age)
30
- message = age >= 18 ? "Adult" : age >= 13 ? "Teen" : "Child"
31
- valid = age >= 0 && age <= 120
32
- {
33
- age: age
34
- category: message
35
- valid: valid ? "yes" : "no"
36
- }
File without changes