rip-lang 2.5.0 → 2.5.1
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 +20 -0
- package/README.md +97 -51
- package/docs/INTERNALS.md +1 -1
- package/docs/PHILOSOPHY.md +1 -1
- package/docs/WHY-YES-RIP.md +3 -3
- package/docs/dist/rip.browser.js +24 -2
- package/docs/dist/rip.browser.min.js +54 -54
- package/docs/dist/rip.browser.min.js.br +0 -0
- package/package.json +1 -1
- package/src/lexer.js +38 -3
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,26 @@ 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.5.1] - 2026-01-16
|
|
11
|
+
|
|
12
|
+
### Template Enhancement
|
|
13
|
+
|
|
14
|
+
**Hyphenated Attributes Work Directly**:
|
|
15
|
+
```coffee
|
|
16
|
+
render
|
|
17
|
+
# Before: needed quoted keys or spread syntax
|
|
18
|
+
i {"data-lucide": "search"}
|
|
19
|
+
|
|
20
|
+
# Now: just works!
|
|
21
|
+
i data-lucide: "search", aria-hidden: "true"
|
|
22
|
+
div data-testid: "container", aria-label: "Menu"
|
|
23
|
+
span data-foo-bar-baz: "multiple-hyphens-work"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The lexer now automatically converts hyphenated attribute names (like `data-*`, `aria-*`) into quoted strings, making HTML-style data attributes intuitive and clean.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
10
30
|
## [2.5.0] - 2026-01-16
|
|
11
31
|
|
|
12
32
|
### Major Release - Parser Optimization + Complete Framework
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
|
-
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/version-2.5.
|
|
12
|
+
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/version-2.5.1-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
14
|
<a href="#"><img src="https://img.shields.io/badge/tests-1046%2F1046-brightgreen.svg" alt="Tests"></a>
|
|
15
15
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg" alt="License"></a>
|
|
@@ -21,10 +21,22 @@
|
|
|
21
21
|
|
|
22
22
|
Rip is a modern reactive language that compiles to JavaScript. It takes the elegant, readable syntax that made CoffeeScript beloved and brings it into the modern era — with ES2022 output, built-in reactivity, and a clean component system for building UIs.
|
|
23
23
|
|
|
24
|
-
The
|
|
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**:
|
|
25
|
+
|
|
26
|
+
```coffee
|
|
27
|
+
count := 0 # Signal (reactive state)
|
|
28
|
+
doubled ~= count * 2 # Derived (auto-updates)
|
|
29
|
+
effect -> log doubled # Effect (side effects)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
No imports. No hooks. No dependency arrays. Just write code.
|
|
33
|
+
|
|
34
|
+
The compiler is completely standalone with **zero dependencies**, and it's self-hosting: Rip compiles itself. At ~14,000 lines of code, it's smaller than CoffeeScript while including a complete reactive framework.
|
|
25
35
|
|
|
26
36
|
**What makes Rip different:**
|
|
27
|
-
- **Reactive primitives** — signals, derived values,
|
|
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 `<=>`
|
|
28
40
|
- **Modern output** — ES2022 with native classes, `?.`, `??`, modules
|
|
29
41
|
- **Zero dependencies** — everything included, even the parser generator
|
|
30
42
|
- **Self-hosting** — `bun run parser` rebuilds the compiler from source
|
|
@@ -164,90 +176,124 @@ fn?(arg) # Safe call
|
|
|
164
176
|
|
|
165
177
|
## Reactivity
|
|
166
178
|
|
|
167
|
-
|
|
179
|
+
**The language IS the framework.** Reactivity is built into Rip's syntax—not a library you import, not hooks you call. Just operators.
|
|
168
180
|
|
|
169
181
|
```coffee
|
|
170
|
-
#
|
|
171
|
-
count
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
# Derived values auto-update
|
|
175
|
-
doubled ~= count * 2
|
|
176
|
-
greeting ~= "Hello, #{name}!"
|
|
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
|
|
177
185
|
|
|
178
|
-
#
|
|
179
|
-
effect
|
|
186
|
+
count = 5 # doubled becomes 10, effect logs "10"
|
|
187
|
+
count = 10 # doubled becomes 20, effect logs "20"
|
|
188
|
+
```
|
|
180
189
|
|
|
181
|
-
|
|
182
|
-
|
|
190
|
+
**Compare to React:**
|
|
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]);
|
|
183
197
|
```
|
|
184
198
|
|
|
185
|
-
|
|
199
|
+
**Rip: 3 lines. React: 5 lines + imports + dependency arrays + hook rules.**
|
|
200
|
+
|
|
201
|
+
| Concept | React | Vue | Solid | Rip |
|
|
202
|
+
|---------|-------|-----|-------|-----|
|
|
203
|
+
| State | `useState()` | `ref()` | `createSignal()` | `x := 0` |
|
|
204
|
+
| Derived | `useMemo()` | `computed()` | `createMemo()` | `x ~= y * 2` |
|
|
205
|
+
| Effect | `useEffect()` | `watch()` | `createEffect()` | `effect ->` |
|
|
206
|
+
|
|
207
|
+
No imports. No hooks. No dependency arrays. Just operators that do what they say.
|
|
208
|
+
|
|
209
|
+
[Full reactivity guide →](docs/GUIDE.md#reactivity)
|
|
186
210
|
|
|
187
211
|
---
|
|
188
212
|
|
|
189
213
|
## Components
|
|
190
214
|
|
|
191
|
-
|
|
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.
|
|
192
216
|
|
|
193
217
|
```coffee
|
|
194
218
|
component Counter
|
|
195
|
-
@label = "Count"
|
|
196
|
-
|
|
197
|
-
|
|
219
|
+
@label = "Count" # Prop with default
|
|
220
|
+
@initial = 0 # Another prop
|
|
221
|
+
|
|
222
|
+
count := @initial # Reactive state (signal)
|
|
223
|
+
doubled ~= count * 2 # Derived value (auto-updates)
|
|
224
|
+
|
|
225
|
+
inc: -> count += 1 # Methods
|
|
198
226
|
dec: -> count -= 1
|
|
199
227
|
|
|
200
228
|
render
|
|
201
229
|
div.counter
|
|
202
230
|
h2 @label
|
|
203
231
|
span.value count
|
|
204
|
-
|
|
232
|
+
span.derived " (×2 = #{doubled})"
|
|
205
233
|
button @click: @dec, "−"
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
**Event handlers** — two patterns:
|
|
209
|
-
```coffee
|
|
210
|
-
# Normal: define methods, reference with @
|
|
211
|
-
inc: -> count += 1
|
|
212
|
-
button @click: @inc, "+"
|
|
234
|
+
button @click: @inc, "+"
|
|
213
235
|
|
|
214
|
-
#
|
|
215
|
-
|
|
236
|
+
# Mount with Ruby-style constructor
|
|
237
|
+
Counter.new(label: "Score", initial: 10).mount "#app"
|
|
216
238
|
```
|
|
217
239
|
|
|
218
|
-
**
|
|
219
|
-
- Props
|
|
220
|
-
-
|
|
221
|
-
-
|
|
222
|
-
-
|
|
240
|
+
**What you get:**
|
|
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
|
|
223
246
|
|
|
224
|
-
[Component guide →](docs/
|
|
247
|
+
[Component guide →](docs/GUIDE.md#components)
|
|
225
248
|
|
|
226
249
|
---
|
|
227
250
|
|
|
228
251
|
## Templates
|
|
229
252
|
|
|
230
|
-
Indentation-based HTML with
|
|
253
|
+
Indentation-based HTML with Pug-style selectors. Templates compile to **fine-grained DOM operations**—when a signal changes, only the affected text node or attribute updates. No virtual DOM, no diffing, no wasted work.
|
|
231
254
|
|
|
232
255
|
```coffee
|
|
233
256
|
render
|
|
234
257
|
div#app.container
|
|
235
258
|
h1.title "Hello, #{name}!"
|
|
236
|
-
|
|
237
|
-
|
|
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
|
|
238
266
|
"Submit"
|
|
267
|
+
|
|
268
|
+
# Loops with keys for efficient updates
|
|
239
269
|
ul.items
|
|
240
|
-
for item in items
|
|
241
|
-
li
|
|
270
|
+
for item in items, key: item.id
|
|
271
|
+
li.item item.name
|
|
242
272
|
```
|
|
243
273
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
274
|
+
**Template features:**
|
|
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
|
|
290
|
+
|
|
291
|
+
# ...replaces all this React ceremony:
|
|
292
|
+
# <input type="number" value={count}
|
|
293
|
+
# onChange={e => setCount(parseInt(e.target.value) || 0)} />
|
|
294
|
+
```
|
|
249
295
|
|
|
250
|
-
[Template guide →](docs/
|
|
296
|
+
[Template guide →](docs/GUIDE.md#templates)
|
|
251
297
|
|
|
252
298
|
---
|
|
253
299
|
|
|
@@ -336,7 +382,7 @@ rip file.rip # Run a file
|
|
|
336
382
|
rip -c file.rip # Compile to JavaScript
|
|
337
383
|
rip -s file.rip # Show S-expressions (debug parser)
|
|
338
384
|
rip -t file.rip # Show tokens (debug lexer)
|
|
339
|
-
bun run test # Run all
|
|
385
|
+
bun run test # Run all 1046 tests
|
|
340
386
|
bun run parser # Rebuild parser (self-hosting!)
|
|
341
387
|
bun run browser # Build browser bundle
|
|
342
388
|
```
|
|
@@ -348,9 +394,9 @@ bun run browser # Build browser bundle
|
|
|
348
394
|
| Guide | Description |
|
|
349
395
|
|-------|-------------|
|
|
350
396
|
| [AGENT.md](AGENT.md) | Complete developer/AI guide |
|
|
351
|
-
| [docs/
|
|
352
|
-
| [docs/
|
|
353
|
-
| [docs/
|
|
397
|
+
| [docs/GUIDE.md](docs/GUIDE.md) | Language guide (reactivity, templates, components) |
|
|
398
|
+
| [docs/INTERNALS.md](docs/INTERNALS.md) | Compiler architecture, S-expressions |
|
|
399
|
+
| [docs/BROWSER.md](docs/BROWSER.md) | Browser usage, REPL |
|
|
354
400
|
| [CONTRIBUTING.md](CONTRIBUTING.md) | How to contribute |
|
|
355
401
|
| [CHANGELOG.md](CHANGELOG.md) | Version history |
|
|
356
402
|
|
package/docs/INTERNALS.md
CHANGED
package/docs/PHILOSOPHY.md
CHANGED
package/docs/WHY-YES-RIP.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
That "Why Not" document makes strong arguments, but here's the **counter-argument**—a working, tested, **production-ready** language that offers a different path.
|
|
8
8
|
|
|
9
|
-
**Rip isn't vaporware. It's real. Version 2.5.
|
|
9
|
+
**Rip isn't vaporware. It's real. Version 2.5.1. 1046/1046 tests passing. Self-hosting. Zero dependencies. Available now.**
|
|
10
10
|
|
|
11
11
|
### The Philosophical Divide: Freedom vs Fear
|
|
12
12
|
|
|
@@ -842,7 +842,7 @@ Rip isn't about going backward. It's about recognizing that **we took a wrong tu
|
|
|
842
842
|
|
|
843
843
|
**The future isn't more dependencies. It's zero dependencies.**
|
|
844
844
|
|
|
845
|
-
**The future is Rip. Version 2.5.
|
|
845
|
+
**The future is Rip. Version 2.5.1. Available today.**
|
|
846
846
|
|
|
847
847
|
---
|
|
848
848
|
|
|
@@ -890,6 +890,6 @@ $ echo 'console.log "Hello, Rip!"' > test.rip && bun test.rip
|
|
|
890
890
|
- ✅ **Two-way binding** (`<=>` operator - automatic for inputs)
|
|
891
891
|
- ✅ **Ruby constructors** (`ClassName.new()` - elegant instantiation)
|
|
892
892
|
|
|
893
|
-
**Version 2.5.
|
|
893
|
+
**Version 2.5.1. Available now. Clone and go.**
|
|
894
894
|
|
|
895
895
|
This approach is ready. Give it a try.
|
package/docs/dist/rip.browser.js
CHANGED
|
@@ -2705,6 +2705,28 @@ Rewriter = function() {
|
|
|
2705
2705
|
}
|
|
2706
2706
|
if (!inRender)
|
|
2707
2707
|
return 1;
|
|
2708
|
+
if (tag === "IDENTIFIER" && !token.spaced) {
|
|
2709
|
+
let parts = [token[1]];
|
|
2710
|
+
let j = i + 1;
|
|
2711
|
+
while (j + 1 < tokens.length) {
|
|
2712
|
+
const hyphen = tokens[j];
|
|
2713
|
+
const nextPart = tokens[j + 1];
|
|
2714
|
+
if (hyphen[0] === "-" && !hyphen.spaced && (nextPart[0] === "IDENTIFIER" || nextPart[0] === "PROPERTY")) {
|
|
2715
|
+
parts.push(nextPart[1]);
|
|
2716
|
+
j += 2;
|
|
2717
|
+
if (nextPart[0] === "PROPERTY")
|
|
2718
|
+
break;
|
|
2719
|
+
} else {
|
|
2720
|
+
break;
|
|
2721
|
+
}
|
|
2722
|
+
}
|
|
2723
|
+
if (parts.length > 1 && j > i + 1 && tokens[j - 1][0] === "PROPERTY") {
|
|
2724
|
+
token[0] = "STRING";
|
|
2725
|
+
token[1] = `"${parts.join("-")}"`;
|
|
2726
|
+
tokens.splice(i + 1, j - i - 1);
|
|
2727
|
+
return 1;
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2708
2730
|
if (tag === ".") {
|
|
2709
2731
|
const prevToken = i > 0 ? tokens[i - 1] : null;
|
|
2710
2732
|
const prevTag = prevToken ? prevToken[0] : null;
|
|
@@ -9314,8 +9336,8 @@ function compileToJS(source, options = {}) {
|
|
|
9314
9336
|
return new Compiler(options).compileToJS(source);
|
|
9315
9337
|
}
|
|
9316
9338
|
// src/browser.js
|
|
9317
|
-
var VERSION = "2.5.
|
|
9318
|
-
var BUILD_DATE = "2026-01-16@
|
|
9339
|
+
var VERSION = "2.5.1";
|
|
9340
|
+
var BUILD_DATE = "2026-01-16@07:47:41GMT";
|
|
9319
9341
|
var dedent = (s) => {
|
|
9320
9342
|
const m = s.match(/^[ \t]*(?=\S)/gm);
|
|
9321
9343
|
const i = Math.min(...(m || []).map((x) => x.length));
|