rip-lang 2.5.1 → 2.7.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 +57 -10
- package/README.md +133 -289
- package/bin/rip +6 -1
- package/docs/BROWSER.md +8 -11
- package/docs/GUIDE.md +101 -923
- package/docs/INTERNALS.md +1 -1
- package/docs/PHILOSOPHY.md +22 -77
- package/docs/REACTIVITY.md +288 -0
- package/docs/WHY-YES-RIP.md +39 -177
- package/docs/dist/rip.browser.js +605 -2453
- package/docs/dist/rip.browser.min.js +283 -359
- package/docs/dist/rip.browser.min.js.br +0 -0
- package/docs/repl.html +94 -437
- package/package.json +4 -1
- package/scripts/serve.js +2 -0
- package/src/compiler.js +73 -2160
- package/src/grammar/grammar.rip +22 -57
- package/src/lexer.js +11 -333
- package/src/parser.js +220 -223
- package/src/repl.js +257 -183
- package/src/tags.js +0 -62
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,53 @@ 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.7.2] - 2026-02-03
|
|
11
|
+
|
|
12
|
+
### Clean ES Module REPL
|
|
13
|
+
|
|
14
|
+
**Proper `vm.SourceTextModule` Implementation**: The REPL now uses Node's standard ES module API instead of temp files:
|
|
15
|
+
|
|
16
|
+
```coffee
|
|
17
|
+
rip> { Cash } = await import("./utils.rip")
|
|
18
|
+
rip> config = Cash((await import("./config.rip")).default)
|
|
19
|
+
rip> config.app.name
|
|
20
|
+
→ 'Rip Labs API'
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Key improvements:**
|
|
24
|
+
- Uses `vm.SourceTextModule` for in-memory module evaluation (no temp files)
|
|
25
|
+
- `.rip` files compiled on-the-fly via module linker
|
|
26
|
+
- Cross-runtime compatible (Node.js, Bun, potentially Deno)
|
|
27
|
+
- Dynamic `await import()` transformed to static imports automatically
|
|
28
|
+
- Clean variable persistence through VM context
|
|
29
|
+
|
|
30
|
+
This is the standard way to handle ES modules in sandboxed contexts - no hacks required.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## [2.7.1] - 2026-02-03
|
|
35
|
+
|
|
36
|
+
### Bun-Native REPL with Dynamic Import Support
|
|
37
|
+
|
|
38
|
+
**Full `import()` Support in REPL**: The REPL now uses Bun's native evaluation instead of Node's `vm` module, enabling dynamic imports:
|
|
39
|
+
|
|
40
|
+
```coffee
|
|
41
|
+
rip> { Cash } = await import("./utils.js")
|
|
42
|
+
rip> config = Cash((await import("./config.js")).default)
|
|
43
|
+
rip> config.app.name
|
|
44
|
+
→ 'My App'
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Key changes:**
|
|
48
|
+
- Replaced `vm.runInContext` with file-based async evaluation
|
|
49
|
+
- Variables persist across REPL lines via `globalThis.__ripRepl`
|
|
50
|
+
- Temp files cleaned up automatically on exit
|
|
51
|
+
- Reactive runtime injected into global scope for cross-line sharing
|
|
52
|
+
|
|
53
|
+
This enables using the REPL for real development workflows with module imports.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
10
57
|
## [2.5.1] - 2026-01-16
|
|
11
58
|
|
|
12
59
|
### Template Enhancement
|
|
@@ -176,7 +223,7 @@ Counter.new().mount("#app") # Selector string support
|
|
|
176
223
|
|
|
177
224
|
| Category | Score | Notes |
|
|
178
225
|
|----------|-------|-------|
|
|
179
|
-
| **Reactivity** | A+ | Fine-grained,
|
|
226
|
+
| **Reactivity** | A+ | Fine-grained, state, effects |
|
|
180
227
|
| **Templates** | A | S-expressions, Pug shorthand |
|
|
181
228
|
| **Components** | A | Props, lifecycle, context API |
|
|
182
229
|
| **Performance** | A | 250× faster parser gen, 51KB bundle |
|
|
@@ -187,7 +234,7 @@ Counter.new().mount("#app") # Selector string support
|
|
|
187
234
|
|
|
188
235
|
**What's in 51KB?**
|
|
189
236
|
- Complete compiler (lexer, parser, code generator)
|
|
190
|
-
- Reactive runtime (
|
|
237
|
+
- Reactive runtime (state, computed values, effects)
|
|
191
238
|
- Template engine (S-expression syntax, dynamic classes)
|
|
192
239
|
- Component system (props, lifecycle, fine-grained updates)
|
|
193
240
|
- Zero dependencies
|
|
@@ -292,10 +339,10 @@ Each item gets its own effect. Changing `selected` updates ONLY the affected cla
|
|
|
292
339
|
### Added - Reactive UI Framework
|
|
293
340
|
|
|
294
341
|
**Phase 1: Reactivity** (previously released)
|
|
295
|
-
-
|
|
296
|
-
-
|
|
297
|
-
- Effects:
|
|
298
|
-
- Runtime: `
|
|
342
|
+
- State-based reactivity: `count := 0` creates reactive state
|
|
343
|
+
- Computed values: `doubled ~= count * 2` auto-tracks dependencies
|
|
344
|
+
- Effects: `~> console.log count` runs on changes
|
|
345
|
+
- Runtime: `__state()`, `__computed()`, `__effect()`, `__batch()`, `__readonly()`
|
|
299
346
|
|
|
300
347
|
**Phase 2: Templates**
|
|
301
348
|
- Indentation-based template syntax in `render` blocks
|
|
@@ -317,8 +364,8 @@ Each item gets its own effect. Changing `selected` updates ONLY the affected cla
|
|
|
317
364
|
- Optional: `@label?`
|
|
318
365
|
- Default: `@label = "default"`
|
|
319
366
|
- Rest: `@...rest`
|
|
320
|
-
- Reactive state within components (auto-
|
|
321
|
-
-
|
|
367
|
+
- Reactive state within components (auto-state)
|
|
368
|
+
- Computed values within components (auto-computed)
|
|
322
369
|
- Component composition: `Button label: "Click"` inside render
|
|
323
370
|
- Children/slots: `@children` prop for nested content
|
|
324
371
|
- Lifecycle hooks: `mounted:`, `unmounted:`
|
|
@@ -332,8 +379,8 @@ Each item gets its own effect. Changing `selected` updates ONLY the affected cla
|
|
|
332
379
|
### Technical Details
|
|
333
380
|
- Components compile to ES6 classes with constructor, render, mount, unmount
|
|
334
381
|
- Props validated at construction (required props throw if missing)
|
|
335
|
-
- State variables become `
|
|
336
|
-
-
|
|
382
|
+
- State variables become `__state()` calls
|
|
383
|
+
- Computed values become `__computed()` calls
|
|
337
384
|
- `mount()` wraps render in `__effect()` for reactive updates
|
|
338
385
|
- PascalCase names in templates trigger component instantiation
|
|
339
386
|
|
package/README.md
CHANGED
|
@@ -5,39 +5,37 @@
|
|
|
5
5
|
<h1 align="center">Rip</h1>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<strong>
|
|
8
|
+
<strong>A modern language that compiles to JavaScript</strong>
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
|
-
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/version-2.
|
|
12
|
+
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/version-2.7.2-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-979%2F979-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
|
-
|
|
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,000 lines of code.
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
**The language IS the framework.** Unlike React, Vue, or Svelte where reactivity comes from libraries or compiler magic, Rip's reactive features are **language-level operators**:
|
|
22
|
+
> **No imports. No hooks. No dependency arrays. Just write code.**
|
|
25
23
|
|
|
26
24
|
```coffee
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
```
|
|
25
|
+
data = fetchUsers! # Dammit operator (call + await)
|
|
26
|
+
user = User.new name: "Alice" # Ruby-style constructor
|
|
27
|
+
squares = (x * x for x in [1..10]) # List comprehension
|
|
31
28
|
|
|
32
|
-
|
|
29
|
+
str =~ /Hello, (\w+)/ # Regex match
|
|
30
|
+
log "Found: #{_[1]}" # Captures in _[1], _[2], etc.
|
|
31
|
+
```
|
|
33
32
|
|
|
34
|
-
|
|
33
|
+
---
|
|
35
34
|
|
|
36
35
|
**What makes Rip different:**
|
|
37
|
-
- **Reactive primitives** — `:=` signals, `~=` derived values, `effect` blocks as syntax
|
|
38
|
-
- **Components as syntax** — `component Counter` with props, lifecycle, fine-grained DOM updates
|
|
39
|
-
- **Templates** — Pug-style HTML in `render` blocks, two-way binding with `<=>`
|
|
40
36
|
- **Modern output** — ES2022 with native classes, `?.`, `??`, modules
|
|
37
|
+
- **New operators** — `!`, `!?`, `//`, `%%`, `=~`, `.new()`, and more
|
|
38
|
+
- **Reactive operators** — `:=`, `~=`, `~>` as language syntax
|
|
41
39
|
- **Zero dependencies** — everything included, even the parser generator
|
|
42
40
|
- **Self-hosting** — `bun run parser` rebuilds the compiler from source
|
|
43
41
|
|
|
@@ -45,263 +43,150 @@ The compiler is completely standalone with **zero dependencies**, and it's self-
|
|
|
45
43
|
|
|
46
44
|
## Installation
|
|
47
45
|
|
|
48
|
-
**Option 1: Install from npm**
|
|
49
46
|
```bash
|
|
50
|
-
|
|
51
|
-
bun add -g rip-lang
|
|
47
|
+
bun add -g rip-lang # Install globally
|
|
52
48
|
```
|
|
53
49
|
|
|
54
|
-
**Option 2: Clone from source**
|
|
55
50
|
```bash
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
**Then use it:**
|
|
61
|
-
```bash
|
|
62
|
-
rip # Interactive REPL
|
|
63
|
-
rip file.rip # Run a file
|
|
64
|
-
rip -c file.rip # Compile to JavaScript
|
|
65
|
-
bun file.rip # Direct execution with Bun loader
|
|
51
|
+
rip # Interactive REPL
|
|
52
|
+
rip file.rip # Run a file
|
|
53
|
+
rip -c file.rip # Compile to JavaScript
|
|
66
54
|
```
|
|
67
55
|
|
|
68
56
|
---
|
|
69
57
|
|
|
70
|
-
## Language
|
|
58
|
+
## Language
|
|
71
59
|
|
|
72
60
|
### Functions & Classes
|
|
61
|
+
|
|
73
62
|
```coffee
|
|
74
|
-
#
|
|
75
|
-
def greet(name) # Named function (hoisted)
|
|
63
|
+
def greet(name) # Named function
|
|
76
64
|
"Hello, #{name}!"
|
|
77
65
|
|
|
78
|
-
add = (a, b) -> a + b
|
|
79
|
-
handler = (e) => @process e
|
|
80
|
-
|
|
81
|
-
# Classes with clean syntax
|
|
82
|
-
class Animal
|
|
83
|
-
constructor: (@name) ->
|
|
84
|
-
speak: -> console.log "#{@name} makes a sound"
|
|
66
|
+
add = (a, b) -> a + b # Arrow function
|
|
67
|
+
handler = (e) => @process e # Fat arrow (preserves this)
|
|
85
68
|
|
|
86
69
|
class Dog extends Animal
|
|
87
|
-
speak: ->
|
|
70
|
+
speak: -> log "#{@name} barks"
|
|
88
71
|
|
|
89
|
-
# Ruby-style
|
|
90
|
-
dog = Dog.new("Buddy") # → new Dog("Buddy")
|
|
91
|
-
dog = new Dog("Buddy") # Traditional JS style
|
|
72
|
+
dog = Dog.new("Buddy") # Ruby-style constructor
|
|
92
73
|
```
|
|
93
74
|
|
|
94
75
|
### Destructuring & Comprehensions
|
|
76
|
+
|
|
95
77
|
```coffee
|
|
96
|
-
# Destructuring
|
|
97
78
|
{name, age} = person
|
|
98
79
|
[first, ...rest] = items
|
|
99
80
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
console.log x for x in items # Just loops (no array created)
|
|
81
|
+
squares = (x * x for x in [1..10]) # Array comprehension
|
|
82
|
+
console.log x for x in items # Loop (no array)
|
|
103
83
|
```
|
|
104
84
|
|
|
105
|
-
### Async &
|
|
85
|
+
### Async & Chaining
|
|
86
|
+
|
|
106
87
|
```coffee
|
|
107
|
-
# Auto-async detection
|
|
108
88
|
def loadUser(id)
|
|
109
|
-
response = await fetch "/api
|
|
89
|
+
response = await fetch "/api/#{id}"
|
|
110
90
|
await response.json()
|
|
111
91
|
|
|
112
|
-
# Optional chaining
|
|
113
|
-
|
|
114
|
-
arr?[0] # CoffeeScript soak
|
|
115
|
-
fn?(arg) # Safe call
|
|
92
|
+
user?.profile?.name # Optional chaining
|
|
93
|
+
data = fetchData! # Await shorthand
|
|
116
94
|
```
|
|
117
95
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
## Unique Features
|
|
121
|
-
|
|
122
|
-
### New Operators & Syntax
|
|
123
|
-
|
|
124
|
-
| Feature | Example | What it does |
|
|
125
|
-
|---------|---------|--------------|
|
|
126
|
-
| **Ruby `.new()`** | `Counter.new()` | Ruby-style constructor → `new Counter()` |
|
|
127
|
-
| **Dammit `!`** | `fetchData!` | Calls the function AND awaits it |
|
|
128
|
-
| **Void `!`** | `def process!` | Suppresses implicit return (always returns undefined) |
|
|
129
|
-
| **Otherwise `!?`** | `val !? 5` | Defaults only if `undefined` (null/0/false are kept!) |
|
|
130
|
-
| **Floor div `//`** | `7 // 2` | Floor division → `Math.floor(7 / 2)` = 3 |
|
|
131
|
-
| **True mod `%%`** | `-1 %% 3` | True modulo (not remainder) → 2, not -1 |
|
|
132
|
-
| **Equal, dammit! `=!`** | `MAX =! 100` | Forces `const` declaration (can't reassign) |
|
|
133
|
-
| **Ternary `?:`** | `x > 0 ? 'yes' : 'no'` | JS-style ternary (plus CoffeeScript's if/then/else) |
|
|
134
|
-
| **Dual optional** | `a?.b` and `a?[0]` | Both ES6 native and CoffeeScript soak styles |
|
|
135
|
-
|
|
136
|
-
### Regex Enhancements
|
|
137
|
-
|
|
138
|
-
| Feature | Example | What it does |
|
|
139
|
-
|---------|---------|--------------|
|
|
140
|
-
| **Match `=~`** | `str =~ /(\w+)/` | Ruby-style regex, captures in `_[1]` |
|
|
141
|
-
| **Regex index** | `str[/pat/, 1]` | Extract capture group directly |
|
|
142
|
-
| **Heregex** | `///pat # comment///` | Extended regex with comments and whitespace |
|
|
143
|
-
|
|
144
|
-
### Strings & Data
|
|
145
|
-
|
|
146
|
-
| Feature | Example | What it does |
|
|
147
|
-
|---------|---------|--------------|
|
|
148
|
-
| **Heredoc** | `'''` closing column | Smart indentation — closing position sets left margin |
|
|
149
|
-
| **`__DATA__`** | `__DATA__\nconfig...` | Ruby-style inline data section, accessible as `DATA` |
|
|
150
|
-
|
|
151
|
-
### Reactivity (Built-in)
|
|
152
|
-
|
|
153
|
-
| Feature | Example | What it does |
|
|
154
|
-
|---------|---------|--------------|
|
|
155
|
-
| **Signal `:=`** | `count := 0` | Creates reactive state container |
|
|
156
|
-
| **Derived `~=`** | `doubled ~= count * 2` | Auto-updates when dependencies change |
|
|
157
|
-
| **Effect** | `effect -> log x` | Runs whenever referenced signals change |
|
|
158
|
-
|
|
159
|
-
### Components & Templates
|
|
160
|
-
|
|
161
|
-
| Feature | Example | What it does |
|
|
162
|
-
|---------|---------|--------------|
|
|
163
|
-
| **Component** | `component Counter` | Define reactive UI component |
|
|
164
|
-
| **Render** | `render` block | Indentation-based HTML templates |
|
|
165
|
-
| **Props** | `@prop`, `@prop?`, `@prop = default` | Component input from parent |
|
|
166
|
-
| **Rest props** | `@...rest` | Capture remaining props |
|
|
167
|
-
| **Two-way bind** | `input value <=> name` | Bidirectional data binding |
|
|
168
|
-
| **Event handlers** | `@click: handler` | DOM event binding |
|
|
169
|
-
| **Lifecycle** | `mounted:`, `unmounted:` | Component lifecycle hooks |
|
|
170
|
-
| **Context API** | `setContext`, `getContext` | Pass data down component tree |
|
|
171
|
-
| **Fine-grained** | No virtual DOM | Surgical DOM updates via signals |
|
|
172
|
-
|
|
173
|
-
**→ 26 major enhancements over CoffeeScript!**
|
|
174
|
-
|
|
175
|
-
---
|
|
96
|
+
### Reactivity
|
|
176
97
|
|
|
177
|
-
|
|
98
|
+
State, computed values, and effects as language operators:
|
|
178
99
|
|
|
179
|
-
|
|
100
|
+
| Operator | Mnemonic | Example | What it does |
|
|
101
|
+
|----------|----------|---------|--------------|
|
|
102
|
+
| `=` | "gets value" | `x = 5` | Regular assignment |
|
|
103
|
+
| `:=` | "has state" | `count := 0` | Reactive state container |
|
|
104
|
+
| `~=` | "always equals" | `twice ~= count * 2` | Auto-updates on changes |
|
|
105
|
+
| `~>` | "reacts to" | `~> log count` | Runs on dependency changes |
|
|
106
|
+
| `=!` | "equals, dammit!" | `MAX =! 100` | Readonly constant |
|
|
180
107
|
|
|
181
|
-
|
|
182
|
-
count := 0 # Signal — reactive state
|
|
183
|
-
doubled ~= count * 2 # Derived — auto-updates when count changes
|
|
184
|
-
effect -> console.log doubled # Effect — runs when dependencies change
|
|
185
|
-
|
|
186
|
-
count = 5 # doubled becomes 10, effect logs "10"
|
|
187
|
-
count = 10 # doubled becomes 20, effect logs "20"
|
|
188
|
-
```
|
|
108
|
+
---
|
|
189
109
|
|
|
190
|
-
|
|
191
|
-
```javascript
|
|
192
|
-
// React: imports, hooks, dependency arrays, rules...
|
|
193
|
-
import { useState, useMemo, useEffect } from 'react';
|
|
194
|
-
const [count, setCount] = useState(0);
|
|
195
|
-
const doubled = useMemo(() => count * 2, [count]);
|
|
196
|
-
useEffect(() => console.log(doubled), [doubled]);
|
|
197
|
-
```
|
|
110
|
+
## New Operators
|
|
198
111
|
|
|
199
|
-
|
|
112
|
+
| Operator | Example | What it does |
|
|
113
|
+
|----------|---------|--------------|
|
|
114
|
+
| `!` (dammit) | `fetchData!` | Calls AND awaits |
|
|
115
|
+
| `!` (void) | `def process!` | Suppresses implicit return |
|
|
116
|
+
| `!?` (otherwise) | `val !? 5` | Default only if `undefined` |
|
|
117
|
+
| `?:` (ternary) | `x > 0 ? 'yes' : 'no'` | JS-style ternary expression |
|
|
118
|
+
| `?.` `?[]` | `a?.b` `a?[0]` | Optional chaining (both styles) |
|
|
119
|
+
| `...` (spread) | `[...rest, last]` | Spread at start or end (JS: end only) |
|
|
120
|
+
| `//` | `7 // 2` | Floor division → 3 |
|
|
121
|
+
| `%%` | `-1 %% 3` | True modulo → 2 |
|
|
122
|
+
| `=~` | `str =~ /Hello, (\w+)/` | Match (captures in `_`) |
|
|
123
|
+
| `[//, n]` | `str[/Hello, (\w+)/, 1]` | Extract capture n |
|
|
124
|
+
| `.new()` | `Dog.new()` | Ruby-style constructor |
|
|
200
125
|
|
|
201
|
-
|
|
202
|
-
|---------|-------|-----|-------|-----|
|
|
203
|
-
| State | `useState()` | `ref()` | `createSignal()` | `x := 0` |
|
|
204
|
-
| Derived | `useMemo()` | `computed()` | `createMemo()` | `x ~= y * 2` |
|
|
205
|
-
| Effect | `useEffect()` | `watch()` | `createEffect()` | `effect ->` |
|
|
126
|
+
**Optional chaining** — Both CoffeeScript and ES6 styles are supported:
|
|
206
127
|
|
|
207
|
-
|
|
128
|
+
| Syntax | Style | Compiles to |
|
|
129
|
+
|--------|-------|-------------|
|
|
130
|
+
| `obj?[0]` | CoffeeScript | `(obj != null ? obj[0] : undefined)` |
|
|
131
|
+
| `fn?(arg)` | CoffeeScript | `(typeof fn === 'function' ? fn(arg) : undefined)` |
|
|
132
|
+
| `obj?.[0]` | ES6/JS | `obj?.[0]` |
|
|
133
|
+
| `fn?.(arg)` | ES6/JS | `fn?.(arg)` |
|
|
208
134
|
|
|
209
|
-
|
|
135
|
+
### Heredoc & Heregex
|
|
210
136
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
## Components
|
|
214
|
-
|
|
215
|
-
Components are a **language construct**, not a pattern. Define with the `component` keyword, get props, state, lifecycle, and fine-grained DOM updates—all without a virtual DOM.
|
|
137
|
+
**Heredoc** — The closing `'''` position sets the left margin (smart dedent):
|
|
216
138
|
|
|
217
139
|
```coffee
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
inc: -> count += 1 # Methods
|
|
226
|
-
dec: -> count -= 1
|
|
227
|
-
|
|
228
|
-
render
|
|
229
|
-
div.counter
|
|
230
|
-
h2 @label
|
|
231
|
-
span.value count
|
|
232
|
-
span.derived " (×2 = #{doubled})"
|
|
233
|
-
button @click: @dec, "−"
|
|
234
|
-
button @click: @inc, "+"
|
|
235
|
-
|
|
236
|
-
# Mount with Ruby-style constructor
|
|
237
|
-
Counter.new(label: "Score", initial: 10).mount "#app"
|
|
140
|
+
html = '''
|
|
141
|
+
<div>
|
|
142
|
+
<p>Hello</p>
|
|
143
|
+
</div>
|
|
144
|
+
'''
|
|
145
|
+
# Result: " <div>\n <p>Hello</p>\n </div>" (note the leading 2 spaces)
|
|
238
146
|
```
|
|
239
147
|
|
|
240
|
-
**
|
|
241
|
-
- **Props:** `@prop` (required), `@prop?` (optional), `@prop = default`
|
|
242
|
-
- **State:** Signals (`:=`) and derived values (`~=`) just work
|
|
243
|
-
- **Lifecycle:** `mounted:`, `unmounted:`, `updated:`
|
|
244
|
-
- **Context:** `setContext`/`getContext` for deep prop passing
|
|
245
|
-
- **Fine-grained updates:** Only changed DOM nodes update—no virtual DOM diffing
|
|
148
|
+
**Heregex** — Extended regex with comments and whitespace:
|
|
246
149
|
|
|
247
|
-
|
|
150
|
+
```coffee
|
|
151
|
+
pattern = ///
|
|
152
|
+
^(\d{3}) # area code
|
|
153
|
+
-(\d{4}) # number
|
|
154
|
+
///
|
|
155
|
+
```
|
|
248
156
|
|
|
249
157
|
---
|
|
250
158
|
|
|
251
|
-
##
|
|
159
|
+
## vs React / Vue / Solid
|
|
160
|
+
|
|
161
|
+
| Concept | React | Vue | Solid | Rip |
|
|
162
|
+
|---------|-------|-----|-------|-----|
|
|
163
|
+
| State | `useState()` | `ref()` | `createSignal()` | `x := 0` |
|
|
164
|
+
| Computed | `useMemo()` | `computed()` | `createMemo()` | `x ~= y * 2` |
|
|
165
|
+
| Effect | `useEffect()` | `watch()` | `createEffect()` | `~> body` |
|
|
252
166
|
|
|
253
|
-
|
|
167
|
+
Rip's reactivity is framework-agnostic — use it with React, Vue, Svelte, or vanilla JS.
|
|
254
168
|
|
|
255
|
-
|
|
256
|
-
render
|
|
257
|
-
div#app.container
|
|
258
|
-
h1.title "Hello, #{name}!"
|
|
259
|
-
|
|
260
|
-
# Two-way binding with <=> operator
|
|
261
|
-
input type: "text", value <=> username
|
|
262
|
-
input type: "number", value <=> count # Auto-uses valueAsNumber!
|
|
263
|
-
|
|
264
|
-
# Dynamic classes (Tailwind-friendly)
|
|
265
|
-
button.btn.("primary" if active) @click: submit
|
|
266
|
-
"Submit"
|
|
267
|
-
|
|
268
|
-
# Loops with keys for efficient updates
|
|
269
|
-
ul.items
|
|
270
|
-
for item in items, key: item.id
|
|
271
|
-
li.item item.name
|
|
272
|
-
```
|
|
169
|
+
---
|
|
273
170
|
|
|
274
|
-
|
|
275
|
-
| Syntax | What it does |
|
|
276
|
-
|--------|--------------|
|
|
277
|
-
| `div#id.class1.class2` | IDs and classes (CSS selector style) |
|
|
278
|
-
| `@click: handler` | Event binding |
|
|
279
|
-
| `@click.prevent.stop:` | Event modifiers |
|
|
280
|
-
| `@keydown.enter:` | Key modifiers |
|
|
281
|
-
| `value <=> var` | Two-way binding (auto-syncs input ↔ variable) |
|
|
282
|
-
| `.("class", cond && "other")` | Dynamic classes |
|
|
283
|
-
| `for x in arr, key: x.id` | Keyed iteration |
|
|
284
|
-
| `span if condition` | Conditional rendering |
|
|
285
|
-
|
|
286
|
-
**The `<=>` operator** handles two-way binding automatically:
|
|
287
|
-
```coffee
|
|
288
|
-
# This one line...
|
|
289
|
-
input type: "number", value <=> count
|
|
171
|
+
## vs CoffeeScript
|
|
290
172
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
173
|
+
| Feature | CoffeeScript | Rip |
|
|
174
|
+
|---------|--------------|-----|
|
|
175
|
+
| **Output** | ES5 (var, prototypes) | ES2022 (classes, `?.`, `??`) |
|
|
176
|
+
| **Reactivity** | None | Built-in |
|
|
177
|
+
| **Dependencies** | Multiple | Zero |
|
|
178
|
+
| **Self-hosting** | No | Yes |
|
|
179
|
+
| **Lexer** | 3,558 LOC | 3,250 LOC |
|
|
180
|
+
| **Compiler** | 10,346 LOC | 5,878 LOC |
|
|
181
|
+
| **Total** | 17,760 LOC | ~10,100 LOC |
|
|
295
182
|
|
|
296
|
-
|
|
183
|
+
Smaller codebase, modern output, built-in reactivity.
|
|
297
184
|
|
|
298
185
|
---
|
|
299
186
|
|
|
300
|
-
## Browser
|
|
187
|
+
## Browser
|
|
301
188
|
|
|
302
|
-
Run Rip directly in the browser (51KB compressed
|
|
303
|
-
|
|
304
|
-
**Try it live:** [https://shreeve.github.io/rip-lang/](https://shreeve.github.io/rip-lang/)
|
|
189
|
+
Run Rip directly in the browser (51KB compressed):
|
|
305
190
|
|
|
306
191
|
```html
|
|
307
192
|
<script src="https://shreeve.github.io/rip-lang/docs/dist/rip.browser.min.js"></script>
|
|
@@ -312,78 +197,50 @@ Run Rip directly in the browser (51KB compressed—complete language + reactive
|
|
|
312
197
|
</script>
|
|
313
198
|
```
|
|
314
199
|
|
|
315
|
-
|
|
316
|
-
bun run browser # Build the bundle
|
|
317
|
-
bun run serve # Start dev server at localhost:3000
|
|
318
|
-
```
|
|
319
|
-
|
|
320
|
-
---
|
|
321
|
-
|
|
322
|
-
## Modernizing What CoffeeScript Started
|
|
323
|
-
|
|
324
|
-
CoffeeScript showed us beautiful syntax. Rip takes that vision further:
|
|
325
|
-
|
|
326
|
-
| | Rip | CoffeeScript |
|
|
327
|
-
|---|---|---|
|
|
328
|
-
| **Output** | ES2022 (classes, `?.`, `??`) | ES5 (var, prototypes) |
|
|
329
|
-
| **Reactivity** | Built-in (signals, effects, templates) | None |
|
|
330
|
-
| **Dependencies** | Zero | Multiple |
|
|
331
|
-
| **Self-hosting** | Yes (compiles itself) | No |
|
|
332
|
-
| **Codebase** | ~14,000 LOC | 17,760 LOC |
|
|
200
|
+
**Try it live:** [shreeve.github.io/rip-lang](https://shreeve.github.io/rip-lang/)
|
|
333
201
|
|
|
334
202
|
---
|
|
335
203
|
|
|
336
|
-
##
|
|
337
|
-
|
|
338
|
-
Traditional compilers use complex AST node classes. Rip uses simple arrays:
|
|
204
|
+
## Architecture
|
|
339
205
|
|
|
340
206
|
```
|
|
341
207
|
Source → Lexer → Parser → S-Expressions → Codegen → JavaScript
|
|
342
208
|
["=", "x", 42]
|
|
343
209
|
```
|
|
344
210
|
|
|
345
|
-
|
|
346
|
-
```javascript
|
|
347
|
-
class BinaryOp {
|
|
348
|
-
constructor(op, left, right) { ... }
|
|
349
|
-
compile() { /* 50 lines */ }
|
|
350
|
-
}
|
|
351
|
-
```
|
|
211
|
+
Simple arrays instead of AST node classes. The compiler is self-hosting — `bun run parser` rebuilds from source.
|
|
352
212
|
|
|
353
|
-
|
|
354
|
-
```javascript
|
|
355
|
-
case '+': return `(${gen(left)} + ${gen(right)})`;
|
|
356
|
-
```
|
|
357
|
-
|
|
358
|
-
| Component | CoffeeScript | Rip |
|
|
359
|
-
|-----------|--------------|-----|
|
|
360
|
-
| Lexer | 3,558 LOC | 3,537 LOC |
|
|
361
|
-
| Parser Generator | 2,285 LOC (Jison) | ~1,000 LOC (Solar) |
|
|
362
|
-
| Compiler | 10,346 LOC | 7,965 LOC |
|
|
363
|
-
| **Total** | **17,760 LOC** | **~14,000 LOC** |
|
|
213
|
+
---
|
|
364
214
|
|
|
365
|
-
|
|
215
|
+
## The Rip Stack
|
|
366
216
|
|
|
367
|
-
|
|
217
|
+
Rip includes optional packages for full-stack development:
|
|
368
218
|
|
|
369
|
-
|
|
219
|
+
| Package | Purpose | Lines |
|
|
220
|
+
|---------|---------|-------|
|
|
221
|
+
| [@rip-lang/api](packages/api/) | Web framework (Sinatra-style) | ~595 |
|
|
222
|
+
| [@rip-lang/server](packages/server/) | Multi-worker process manager | ~1,110 |
|
|
223
|
+
| [@rip-lang/db](packages/db/) | DuckDB HTTP server | ~225 |
|
|
224
|
+
| [@rip-lang/schema](packages/schema/) | ORM with reactive models | ~420 |
|
|
370
225
|
|
|
371
|
-
|
|
226
|
+
```bash
|
|
227
|
+
bun add @rip-lang/api @rip-lang/server
|
|
228
|
+
```
|
|
372
229
|
|
|
373
|
-
|
|
230
|
+
[Full stack documentation →](packages/README.md)
|
|
374
231
|
|
|
375
232
|
---
|
|
376
233
|
|
|
377
234
|
## Quick Reference
|
|
378
235
|
|
|
379
236
|
```bash
|
|
380
|
-
rip #
|
|
381
|
-
rip file.rip # Run
|
|
382
|
-
rip -c file.rip # Compile
|
|
383
|
-
rip -
|
|
384
|
-
rip -
|
|
385
|
-
bun run test #
|
|
386
|
-
bun run parser # Rebuild parser
|
|
237
|
+
rip # REPL
|
|
238
|
+
rip file.rip # Run
|
|
239
|
+
rip -c file.rip # Compile
|
|
240
|
+
rip -t file.rip # Tokens
|
|
241
|
+
rip -s file.rip # S-expressions
|
|
242
|
+
bun run test # 979 tests
|
|
243
|
+
bun run parser # Rebuild parser
|
|
387
244
|
bun run browser # Build browser bundle
|
|
388
245
|
```
|
|
389
246
|
|
|
@@ -393,12 +250,11 @@ bun run browser # Build browser bundle
|
|
|
393
250
|
|
|
394
251
|
| Guide | Description |
|
|
395
252
|
|-------|-------------|
|
|
396
|
-
| [
|
|
397
|
-
| [docs/
|
|
398
|
-
| [docs/INTERNALS.md](docs/INTERNALS.md) | Compiler architecture
|
|
399
|
-
| [
|
|
253
|
+
| [docs/GUIDE.md](docs/GUIDE.md) | Language guide |
|
|
254
|
+
| [docs/REACTIVITY.md](docs/REACTIVITY.md) | Reactivity deep dive |
|
|
255
|
+
| [docs/INTERNALS.md](docs/INTERNALS.md) | Compiler architecture |
|
|
256
|
+
| [packages/](packages/README.md) | Full-stack packages |
|
|
400
257
|
| [CONTRIBUTING.md](CONTRIBUTING.md) | How to contribute |
|
|
401
|
-
| [CHANGELOG.md](CHANGELOG.md) | Version history |
|
|
402
258
|
|
|
403
259
|
---
|
|
404
260
|
|
|
@@ -408,7 +264,7 @@ bun run browser # Build browser bundle
|
|
|
408
264
|
{ "dependencies": {} }
|
|
409
265
|
```
|
|
410
266
|
|
|
411
|
-
Everything included: compiler, parser generator
|
|
267
|
+
Everything included: compiler, parser generator, REPL, browser bundle, test framework.
|
|
412
268
|
|
|
413
269
|
---
|
|
414
270
|
|
|
@@ -416,24 +272,12 @@ Everything included: compiler, parser generator (solar.rip), REPL, browser bundl
|
|
|
416
272
|
|
|
417
273
|
> *Simplicity scales.*
|
|
418
274
|
|
|
419
|
-
|
|
275
|
+
Simple IR (S-expressions), clear pipeline (lex → parse → generate), minimal code, comprehensive tests.
|
|
420
276
|
|
|
421
277
|
---
|
|
422
278
|
|
|
423
|
-
|
|
279
|
+
**Inspired by:** CoffeeScript, Lisp, Ruby | **Powered by:** [Bun](https://bun.sh)
|
|
424
280
|
|
|
425
|
-
|
|
281
|
+
MIT License
|
|
426
282
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
---
|
|
430
|
-
|
|
431
|
-
## License
|
|
432
|
-
|
|
433
|
-
MIT
|
|
434
|
-
|
|
435
|
-
---
|
|
436
|
-
|
|
437
|
-
<p align="center">
|
|
438
|
-
<strong>Start simple. Build incrementally. Ship elegantly.</strong> ✨
|
|
439
|
-
</p>
|
|
283
|
+
<p align="center"><strong>Start simple. Build incrementally. Ship elegantly.</strong></p>
|