rip-lang 3.4.3 → 3.4.5
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 +40 -1
- package/README.md +39 -29
- package/docs/RIP-GUIDE.md +598 -0
- package/docs/RIP-INTERNALS.md +22 -24
- package/docs/RIP-LANG.md +4 -2
- package/docs/RIP-REACTIVITY.md +3 -1
- package/docs/RIP-TYPES.md +2 -0
- package/docs/dist/rip.browser.js +3 -3
- package/docs/dist/rip.browser.min.js +1 -1
- package/docs/dist/rip.browser.min.js.br +0 -0
- package/docs/index.html +11 -7
- package/package.json +3 -3
- package/src/compiler.js +1 -1
- package/docs/example.html +0 -177
- /package/src/{sourcemap.js → sourcemaps.js} +0 -0
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
|
+
## [3.4.3] - 2026-02-09
|
|
11
|
+
|
|
12
|
+
### Source Maps & IDE Intelligence
|
|
13
|
+
|
|
14
|
+
- **Source Map V3 support** — New `src/sourcemaps.js` implements ECMA-426 source maps with zero dependencies. VLQ encoder + SourceMapGenerator class in ~120 lines.
|
|
15
|
+
- **Inline source maps** — `-m` flag embeds source maps as base64 data URLs in compiled output. One file for everything — debuggers read them natively.
|
|
16
|
+
- **Reverse source maps** — `toReverseMap()` provides O(1) source→generated position lookup for IDE type intelligence.
|
|
17
|
+
- **S-expression locations** — Parser now attaches `.loc = {r, c}` on every S-expression node. Locations flow from lexer through parser to code generator using consistent `{r, c}` naming.
|
|
18
|
+
- **Parser cleanup** — Removed legacy Jison location format (`first_line`/`first_column`), dead `ranges` variable, and `locFirst`/`locLast` extraction. Parser uses `{r, c}` natively.
|
|
19
|
+
- **VS Code Extension v0.3.1** — Level 2 type intelligence: autocomplete, hover, and go-to-definition from third-party `.d.ts` files inside `.rip` files. Shadow `.ts` compilation with 300ms debounce. Configurable via `rip.types.intellisense` setting.
|
|
20
|
+
|
|
21
|
+
## [3.3.1] - 2026-02-09
|
|
22
|
+
|
|
23
|
+
### Playground & Extension
|
|
24
|
+
|
|
25
|
+
- **Playground cleanup** — Eliminated dead CSS rules, extracted shared Monaco config, DRYed up toggle handlers, fixed flicker by restoring button states before page reveal, added smooth fade-in transition, defaulted light/dark mode to system `prefers-color-scheme`.
|
|
26
|
+
- **VS Code Extension v0.2.0** — Auto-generate `.d.ts` files on save, commands for single-file and workspace-wide type generation, auto-detect compiler binary, configurable settings.
|
|
27
|
+
- **Extension published** to VS Code Marketplace as `rip-lang.rip`.
|
|
28
|
+
|
|
29
|
+
## [3.2.1] - 2026-02-08
|
|
30
|
+
|
|
31
|
+
### Test Suite & Solar Cleanup
|
|
32
|
+
|
|
33
|
+
- **Test suite overhaul** — Redistributed tests from `stabilization.rip` and `compatibility.rip` into proper files, removed duplicates, added `reactivity.rip` and `types.rip` test files, added `for-as` guard tests. Now 1,140 tests.
|
|
34
|
+
- **Solar parser generator cleanup** — Removed ~79 lines of dead Jison compatibility code, optimized runtime parser with `.call` instead of `.apply`, modernized variable naming from `yy` prefixes.
|
|
35
|
+
|
|
36
|
+
## [3.2.0] - 2026-02-08
|
|
37
|
+
|
|
38
|
+
### Rip Types — Optional Type System
|
|
39
|
+
|
|
40
|
+
- **Type annotations** (`::`) on variables, parameters, and return types — compile-time only, stripped from JS output.
|
|
41
|
+
- **Type aliases** (`::=`) for named types, structural types, union types.
|
|
42
|
+
- **Interfaces** with `extends` support.
|
|
43
|
+
- **Enums** with runtime JS generation and `.d.ts` emission.
|
|
44
|
+
- **Generics** (`<T>`) for reusable type definitions.
|
|
45
|
+
- **`.d.ts` emission** — `emitTypes()` generates TypeScript declaration files directly from annotated token stream.
|
|
46
|
+
- **CLI flag** — `-d`/`--dts` generates `.d.ts` files alongside compiled JS.
|
|
47
|
+
- **Architecture** — All type logic consolidated in `src/types.js` (lexer sidecar), minimal changes to lexer and compiler. Added `::` and `::=` operators to lexer.
|
|
48
|
+
|
|
10
49
|
## [3.1.0] - 2026-02-08
|
|
11
50
|
|
|
12
51
|
### Rip UI — Zero-Build Reactive Web Framework
|
|
@@ -602,7 +641,7 @@ All 962 tests passing (100%) ✅
|
|
|
602
641
|
- Zero performance impact, much more maintainable
|
|
603
642
|
|
|
604
643
|
- **Added inline SVG favicon to HTML files**
|
|
605
|
-
-
|
|
644
|
+
- index.html now has embedded favicon
|
|
606
645
|
- No HTTP request needed for favicon
|
|
607
646
|
- Works in all contexts (local, GitHub Pages, offline)
|
|
608
647
|
|
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.4.3-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-1140%2F1140-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
|
|
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,300 lines of code.
|
|
21
21
|
|
|
22
22
|
> **No imports. No hooks. No dependency arrays. Just write code.**
|
|
23
23
|
|
|
@@ -254,9 +254,9 @@ See [@rip-lang/ui](packages/ui/) for the full framework: Virtual File System, fi
|
|
|
254
254
|
| **Reactivity** | None | Built-in |
|
|
255
255
|
| **Dependencies** | Multiple | Zero |
|
|
256
256
|
| **Self-hosting** | No | Yes |
|
|
257
|
-
| **Lexer** | 3,558 LOC | 1,
|
|
258
|
-
| **Compiler** | 10,346 LOC | 3,
|
|
259
|
-
| **Total** | 17,760 LOC | ~
|
|
257
|
+
| **Lexer** | 3,558 LOC | 1,867 LOC |
|
|
258
|
+
| **Compiler** | 10,346 LOC | 3,292 LOC |
|
|
259
|
+
| **Total** | 17,760 LOC | ~10,300 LOC |
|
|
260
260
|
|
|
261
261
|
Smaller codebase, modern output, built-in reactivity.
|
|
262
262
|
|
|
@@ -282,22 +282,26 @@ Run Rip directly in the browser:
|
|
|
282
282
|
## Architecture
|
|
283
283
|
|
|
284
284
|
```
|
|
285
|
-
Source -> Lexer -> Parser -> S-Expressions -> Codegen -> JavaScript
|
|
286
|
-
(1,
|
|
285
|
+
Source -> Lexer -> emitTypes -> Parser -> S-Expressions -> Codegen -> JavaScript
|
|
286
|
+
(1,867) (types.js) (357) ["=", "x", 42] (3,292) + source map
|
|
287
287
|
```
|
|
288
288
|
|
|
289
|
-
Simple arrays instead of AST node classes. The compiler is self-hosting — `bun run parser` rebuilds from source.
|
|
289
|
+
Simple arrays (with `.loc`) instead of AST node classes. The compiler is self-hosting — `bun run parser` rebuilds from source.
|
|
290
290
|
|
|
291
291
|
| Component | File | Lines |
|
|
292
292
|
|-----------|------|-------|
|
|
293
|
-
| Lexer + Rewriter | `src/lexer.js` | 1,
|
|
294
|
-
| Compiler + Codegen | `src/compiler.js` | 3,
|
|
295
|
-
|
|
|
296
|
-
|
|
|
297
|
-
|
|
|
298
|
-
|
|
|
299
|
-
|
|
|
300
|
-
|
|
|
293
|
+
| Lexer + Rewriter | `src/lexer.js` | 1,867 |
|
|
294
|
+
| Compiler + Codegen | `src/compiler.js` | 3,292 |
|
|
295
|
+
| Type System | `src/types.js` | 719 |
|
|
296
|
+
| Component System | `src/components.js` | 1,240 |
|
|
297
|
+
| Source Maps | `src/sourcemaps.js` | 122 |
|
|
298
|
+
| Parser (generated) | `src/parser.js` | 357 |
|
|
299
|
+
| Grammar | `src/grammar/grammar.rip` | 935 |
|
|
300
|
+
| Parser Generator | `src/grammar/solar.rip` | 916 |
|
|
301
|
+
| REPL | `src/repl.js` | 707 |
|
|
302
|
+
| Browser Entry | `src/browser.js` | 80 |
|
|
303
|
+
| Tags | `src/tags.js` | 63 |
|
|
304
|
+
| **Total** | | **10,298** |
|
|
301
305
|
|
|
302
306
|
---
|
|
303
307
|
|
|
@@ -305,12 +309,17 @@ Simple arrays instead of AST node classes. The compiler is self-hosting — `bun
|
|
|
305
309
|
|
|
306
310
|
Rip includes optional packages for full-stack development:
|
|
307
311
|
|
|
308
|
-
| Package |
|
|
309
|
-
|
|
310
|
-
| [
|
|
311
|
-
| [@rip-lang/api](packages/api/) | HTTP framework (Sinatra-style routing, 37 validators) |
|
|
312
|
-
| [@rip-lang/server](packages/server/) | Multi-worker app server (hot reload, HTTPS, mDNS) |
|
|
313
|
-
| [@rip-lang/db](packages/db/) | DuckDB server with official UI (pure Bun FFI) |
|
|
312
|
+
| Package | Version | Purpose |
|
|
313
|
+
|---------|---------|---------|
|
|
314
|
+
| [rip-lang](https://www.npmjs.com/package/rip-lang) | 3.4.4 | Core language compiler |
|
|
315
|
+
| [@rip-lang/api](packages/api/) | 1.1.4 | HTTP framework (Sinatra-style routing, 37 validators) |
|
|
316
|
+
| [@rip-lang/server](packages/server/) | 1.1.3 | Multi-worker app server (hot reload, HTTPS, mDNS) |
|
|
317
|
+
| [@rip-lang/db](packages/db/) | 1.1.2 | DuckDB server with official UI (pure Bun FFI) |
|
|
318
|
+
| [@rip-lang/ui](packages/ui/) | 0.1.2 | Zero-build reactive web framework (VFS, router, components) |
|
|
319
|
+
| [@rip-lang/swarm](packages/swarm/) | 1.1.1 | Parallel job runner with worker pool |
|
|
320
|
+
| [@rip-lang/csv](packages/csv/) | 1.1.1 | CSV parser + writer |
|
|
321
|
+
| [@rip-lang/schema](packages/schema/) | 0.1.0 | ORM + validation |
|
|
322
|
+
| [VS Code Extension](packages/vscode/) | 0.3.1 | Syntax highlighting, type intelligence, source maps |
|
|
314
323
|
|
|
315
324
|
```bash
|
|
316
325
|
bun add -g @rip-lang/db # Installs everything (rip-lang + api + db)
|
|
@@ -348,7 +357,7 @@ rip file.rip # Run
|
|
|
348
357
|
rip -c file.rip # Compile
|
|
349
358
|
rip -t file.rip # Tokens
|
|
350
359
|
rip -s file.rip # S-expressions
|
|
351
|
-
bun run test #
|
|
360
|
+
bun run test # 1140 tests
|
|
352
361
|
bun run parser # Rebuild parser
|
|
353
362
|
bun run browser # Build browser bundle
|
|
354
363
|
```
|
|
@@ -359,11 +368,12 @@ bun run browser # Build browser bundle
|
|
|
359
368
|
|
|
360
369
|
| Guide | Description |
|
|
361
370
|
|-------|-------------|
|
|
362
|
-
| [docs/RIP-
|
|
363
|
-
| [
|
|
364
|
-
| [docs/RIP-
|
|
365
|
-
| [
|
|
366
|
-
| [
|
|
371
|
+
| [docs/RIP-GUIDE.md](docs/RIP-GUIDE.md) | Users / AI — practical guide for using Rip in projects |
|
|
372
|
+
| [AGENT.md](AGENT.md) | AI agents — get up to speed for working on the compiler |
|
|
373
|
+
| [docs/RIP-LANG.md](docs/RIP-LANG.md) | Users — full language reference |
|
|
374
|
+
| [docs/RIP-TYPES.md](docs/RIP-TYPES.md) | Contributors — type system specification |
|
|
375
|
+
| [docs/RIP-REACTIVITY.md](docs/RIP-REACTIVITY.md) | Users — reactivity deep dive |
|
|
376
|
+
| [docs/RIP-INTERNALS.md](docs/RIP-INTERNALS.md) | Contributors — compiler architecture |
|
|
367
377
|
|
|
368
378
|
---
|
|
369
379
|
|
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
<img src="https://raw.githubusercontent.com/shreeve/rip-lang/main/docs/rip.png" style="width:50px" /> <br>
|
|
2
|
+
|
|
3
|
+
# Rip Guide
|
|
4
|
+
|
|
5
|
+
A practical guide for using Rip in your projects. Rip is a modern language that compiles to ES2022 JavaScript. It runs on [Bun](https://bun.sh) and has zero dependencies.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Getting Started
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Install Bun (if needed)
|
|
13
|
+
curl -fsSL https://bun.sh/install | bash
|
|
14
|
+
|
|
15
|
+
# Install Rip
|
|
16
|
+
bun add -g rip-lang
|
|
17
|
+
|
|
18
|
+
# Run a file
|
|
19
|
+
rip app.rip
|
|
20
|
+
|
|
21
|
+
# Interactive REPL
|
|
22
|
+
rip
|
|
23
|
+
|
|
24
|
+
# Compile to JavaScript
|
|
25
|
+
rip -c app.rip
|
|
26
|
+
|
|
27
|
+
# Compile with source map
|
|
28
|
+
rip -cm app.rip
|
|
29
|
+
|
|
30
|
+
# Generate .d.ts type declarations
|
|
31
|
+
rip -d app.rip
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Rip files use the `.rip` extension. Bun runs them directly via `bun app.rip` or `rip app.rip`.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Language Basics
|
|
39
|
+
|
|
40
|
+
Rip uses **significant whitespace** (indentation, not braces) and **implicit returns** (the last expression is the return value). Semicolons and parentheses are optional in most contexts.
|
|
41
|
+
|
|
42
|
+
### Variables
|
|
43
|
+
|
|
44
|
+
```coffee
|
|
45
|
+
# Assignment (compiles to let)
|
|
46
|
+
name = "Alice"
|
|
47
|
+
count = 0
|
|
48
|
+
|
|
49
|
+
# Constant ("equals, dammit!")
|
|
50
|
+
MAX =! 100
|
|
51
|
+
|
|
52
|
+
# Destructuring
|
|
53
|
+
{name, age} = person
|
|
54
|
+
[first, ...rest] = items
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Strings
|
|
58
|
+
|
|
59
|
+
```coffee
|
|
60
|
+
# Interpolation (both styles work)
|
|
61
|
+
greeting = "Hello, #{name}!"
|
|
62
|
+
greeting = "Hello, ${name}!"
|
|
63
|
+
|
|
64
|
+
# Heredocs (closing delimiter sets left margin)
|
|
65
|
+
html = """
|
|
66
|
+
<div>
|
|
67
|
+
<p>Hello</p>
|
|
68
|
+
</div>
|
|
69
|
+
"""
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Functions
|
|
73
|
+
|
|
74
|
+
```coffee
|
|
75
|
+
# Named function
|
|
76
|
+
def greet(name)
|
|
77
|
+
"Hello, #{name}!"
|
|
78
|
+
|
|
79
|
+
# Arrow function
|
|
80
|
+
add = (a, b) -> a + b
|
|
81
|
+
|
|
82
|
+
# Fat arrow (preserves this)
|
|
83
|
+
handler = (e) => @process(e)
|
|
84
|
+
|
|
85
|
+
# Default parameters
|
|
86
|
+
def greet(name = "World")
|
|
87
|
+
"Hello, #{name}!"
|
|
88
|
+
|
|
89
|
+
# Void function (suppresses return)
|
|
90
|
+
def log!(message)
|
|
91
|
+
console.log message
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Classes
|
|
95
|
+
|
|
96
|
+
```coffee
|
|
97
|
+
class Animal
|
|
98
|
+
constructor: (@name) ->
|
|
99
|
+
|
|
100
|
+
speak: -> "#{@name} makes a sound"
|
|
101
|
+
|
|
102
|
+
class Dog extends Animal
|
|
103
|
+
speak: -> "#{@name} barks!"
|
|
104
|
+
|
|
105
|
+
# Ruby-style instantiation
|
|
106
|
+
dog = Dog.new "Buddy"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Control Flow
|
|
110
|
+
|
|
111
|
+
```coffee
|
|
112
|
+
# If/else
|
|
113
|
+
if user.admin
|
|
114
|
+
showAdmin()
|
|
115
|
+
else
|
|
116
|
+
showUser()
|
|
117
|
+
|
|
118
|
+
# Ternary
|
|
119
|
+
status = active ? "on" : "off"
|
|
120
|
+
|
|
121
|
+
# Postfix
|
|
122
|
+
console.log "hi" if ready
|
|
123
|
+
return unless valid
|
|
124
|
+
|
|
125
|
+
# Switch
|
|
126
|
+
result = switch status
|
|
127
|
+
when "active" then "Running"
|
|
128
|
+
when "done" then "Complete"
|
|
129
|
+
else "Unknown"
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Loops
|
|
133
|
+
|
|
134
|
+
```coffee
|
|
135
|
+
# Arrays
|
|
136
|
+
for item in items
|
|
137
|
+
console.log item
|
|
138
|
+
|
|
139
|
+
for item, i in items # with index
|
|
140
|
+
console.log "#{i}: #{item}"
|
|
141
|
+
|
|
142
|
+
# Objects
|
|
143
|
+
for key, value of object
|
|
144
|
+
console.log "#{key} = #{value}"
|
|
145
|
+
|
|
146
|
+
# Ranges
|
|
147
|
+
for i in [1..10] # inclusive (1 to 10)
|
|
148
|
+
for i in [1...10] # exclusive (1 to 9)
|
|
149
|
+
|
|
150
|
+
# Comprehensions
|
|
151
|
+
squares = (x * x for x in [1..10])
|
|
152
|
+
evens = (x for x in items when x % 2 is 0)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Operators
|
|
158
|
+
|
|
159
|
+
Rip extends JavaScript with powerful operators:
|
|
160
|
+
|
|
161
|
+
| Operator | Name | Example | Result |
|
|
162
|
+
|----------|------|---------|--------|
|
|
163
|
+
| `!` | Dammit | `fetchData!` | `await fetchData()` |
|
|
164
|
+
| `!` | Void | `def log!` | Function returns `undefined` |
|
|
165
|
+
| `=!` | Readonly | `MAX =! 100` | `const MAX = 100` |
|
|
166
|
+
| `!?` | Otherwise | `val !? 5` | Default if undefined/throws |
|
|
167
|
+
| `?` | Existence | `x?` | `x != null` |
|
|
168
|
+
| `?.` | Optional chain | `a?.b?.c` | ES6 optional chaining |
|
|
169
|
+
| `?[]` | Optional index | `arr?[0]` | `arr?.[0]` |
|
|
170
|
+
| `??` | Nullish | `a ?? b` | ES6 nullish coalescing |
|
|
171
|
+
| `//` | Floor div | `7 // 2` | `3` |
|
|
172
|
+
| `%%` | True modulo | `-1 %% 3` | `2` (always positive) |
|
|
173
|
+
| `=~` | Regex match | `str =~ /pat/` | Match, captures in `_` |
|
|
174
|
+
| `:=` | State | `count := 0` | Reactive signal |
|
|
175
|
+
| `~=` | Computed | `doubled ~= x * 2` | Reactive computed |
|
|
176
|
+
| `~>` | Effect | `~> console.log x` | Reactive side effect |
|
|
177
|
+
| `**` | Power | `2 ** 10` | `1024` |
|
|
178
|
+
| `..` | Range | `[1..5]` | Inclusive range |
|
|
179
|
+
| `...` | Spread/rest | `[...a, ...b]` | ES6 spread |
|
|
180
|
+
|
|
181
|
+
### Dammit Operator (`!`)
|
|
182
|
+
|
|
183
|
+
The most distinctive Rip operator. Appended to a function call, it both calls AND awaits:
|
|
184
|
+
|
|
185
|
+
```coffee
|
|
186
|
+
# These are equivalent:
|
|
187
|
+
data = fetchUsers!
|
|
188
|
+
data = await fetchUsers()
|
|
189
|
+
|
|
190
|
+
# With arguments:
|
|
191
|
+
user = getUser!(id)
|
|
192
|
+
user = await getUser(id)
|
|
193
|
+
|
|
194
|
+
# Functions are auto-async when they contain !
|
|
195
|
+
def loadData(id)
|
|
196
|
+
user = getUser!(id)
|
|
197
|
+
posts = getPosts!(user.id)
|
|
198
|
+
{user, posts}
|
|
199
|
+
# Compiles to: async function loadData(id) { ... }
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Regex Match (`=~`)
|
|
203
|
+
|
|
204
|
+
Ruby-style pattern matching with captures stored in `_`:
|
|
205
|
+
|
|
206
|
+
```coffee
|
|
207
|
+
if text =~ /Hello, (\w+)/
|
|
208
|
+
console.log "Found: #{_[1]}"
|
|
209
|
+
|
|
210
|
+
# Direct extraction via regex indexing
|
|
211
|
+
domain = "user@example.com"[/@(.+)$/, 1] # "example.com"
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Guard Clauses
|
|
215
|
+
|
|
216
|
+
```coffee
|
|
217
|
+
def loadUser(id)
|
|
218
|
+
data = fetchUser!(id) or return {error: "Not found"}
|
|
219
|
+
token = headers.auth or throw new Error "No auth"
|
|
220
|
+
port = config.port ?? return 3000
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Implicit Commas
|
|
224
|
+
|
|
225
|
+
When a literal value is followed by an arrow function, Rip inserts a comma automatically:
|
|
226
|
+
|
|
227
|
+
```coffee
|
|
228
|
+
# Clean route handlers
|
|
229
|
+
get '/users' -> User.all!
|
|
230
|
+
get '/users/:id' -> User.find params.id
|
|
231
|
+
post '/users' -> User.create body
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Reactivity
|
|
237
|
+
|
|
238
|
+
Rip provides fine-grained reactivity as language-level operators, not library imports:
|
|
239
|
+
|
|
240
|
+
```coffee
|
|
241
|
+
# State — reactive container
|
|
242
|
+
count := 0
|
|
243
|
+
|
|
244
|
+
# Computed — derived value (lazy, cached)
|
|
245
|
+
doubled ~= count * 2
|
|
246
|
+
message ~= "Count is #{count}"
|
|
247
|
+
|
|
248
|
+
# Effect — side effect, re-runs when dependencies change
|
|
249
|
+
~> console.log "Count changed to #{count}"
|
|
250
|
+
|
|
251
|
+
# Update state (triggers dependents)
|
|
252
|
+
count = 5
|
|
253
|
+
# doubled is now 10
|
|
254
|
+
# effect logs: "Count changed to 5"
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Reactive Methods
|
|
258
|
+
|
|
259
|
+
```coffee
|
|
260
|
+
count := 0
|
|
261
|
+
count.read() # Get value without tracking
|
|
262
|
+
count.lock() # Make readonly
|
|
263
|
+
count.free() # Unsubscribe from dependencies
|
|
264
|
+
count.kill() # Clean up, return final value
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## Type Annotations
|
|
270
|
+
|
|
271
|
+
Rip's optional type system adds annotations that are **erased** from JavaScript output and **emitted** as `.d.ts` files for TypeScript interoperability.
|
|
272
|
+
|
|
273
|
+
```coffee
|
|
274
|
+
# Annotate parameters and return types
|
|
275
|
+
def greet(name:: string):: string
|
|
276
|
+
"Hello, #{name}!"
|
|
277
|
+
|
|
278
|
+
# Type aliases
|
|
279
|
+
User ::= type
|
|
280
|
+
id: number
|
|
281
|
+
name: string
|
|
282
|
+
email?: string # optional property
|
|
283
|
+
|
|
284
|
+
# Interfaces
|
|
285
|
+
interface Animal
|
|
286
|
+
name: string
|
|
287
|
+
speak: => void
|
|
288
|
+
|
|
289
|
+
# Enums (emit runtime JS + .d.ts)
|
|
290
|
+
enum Status
|
|
291
|
+
Active
|
|
292
|
+
Inactive
|
|
293
|
+
Pending
|
|
294
|
+
|
|
295
|
+
# Generate .d.ts
|
|
296
|
+
# rip -d myfile.rip
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## Modules
|
|
302
|
+
|
|
303
|
+
Standard ES6 module syntax:
|
|
304
|
+
|
|
305
|
+
```coffee
|
|
306
|
+
# Import
|
|
307
|
+
import express from 'express'
|
|
308
|
+
import { readFile } from 'fs'
|
|
309
|
+
import * as path from 'path'
|
|
310
|
+
|
|
311
|
+
# Export
|
|
312
|
+
export def processData(data)
|
|
313
|
+
data.map (x) -> x * 2
|
|
314
|
+
|
|
315
|
+
export default { process: processData }
|
|
316
|
+
|
|
317
|
+
# Re-export
|
|
318
|
+
export { foo, bar } from './utils'
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## Async Patterns
|
|
324
|
+
|
|
325
|
+
```coffee
|
|
326
|
+
# Traditional await
|
|
327
|
+
user = await getUser(id)
|
|
328
|
+
|
|
329
|
+
# Dammit operator (call + await)
|
|
330
|
+
user = getUser!(id)
|
|
331
|
+
posts = getPosts!(user.id)
|
|
332
|
+
|
|
333
|
+
# Error handling
|
|
334
|
+
try
|
|
335
|
+
data = fetchData!
|
|
336
|
+
catch error
|
|
337
|
+
console.error error
|
|
338
|
+
|
|
339
|
+
# Async iteration
|
|
340
|
+
for item as! asyncIterable
|
|
341
|
+
console.log item
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## Packages
|
|
347
|
+
|
|
348
|
+
Rip includes optional packages for full-stack development. All are written in Rip, have zero dependencies, and run on Bun.
|
|
349
|
+
|
|
350
|
+
```bash
|
|
351
|
+
bun add @rip-lang/api # Web framework
|
|
352
|
+
bun add @rip-lang/server # Production server
|
|
353
|
+
bun add @rip-lang/db # DuckDB server
|
|
354
|
+
bun add @rip-lang/ui # Reactive web UI
|
|
355
|
+
bun add @rip-lang/schema # ORM + validation
|
|
356
|
+
bun add @rip-lang/swarm # Parallel job runner
|
|
357
|
+
bun add @rip-lang/csv # CSV parser + writer
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### @rip-lang/api — Web Framework
|
|
361
|
+
|
|
362
|
+
Sinatra-style routing with `@` context magic and 37 built-in validators.
|
|
363
|
+
|
|
364
|
+
```coffee
|
|
365
|
+
import { get, post, use, read, start, notFound } from '@rip-lang/api'
|
|
366
|
+
|
|
367
|
+
# Routes — return data directly
|
|
368
|
+
get '/' -> { message: 'Hello!' }
|
|
369
|
+
get '/users/:id' -> User.find!(read 'id', 'id!')
|
|
370
|
+
|
|
371
|
+
# Form validation with read()
|
|
372
|
+
post '/signup' ->
|
|
373
|
+
email = read 'email', 'email!' # required email
|
|
374
|
+
age = read 'age', 'int', [18, 120] # integer between 18-120
|
|
375
|
+
role = read 'role', ['admin', 'user'] # enum
|
|
376
|
+
{ success: true, email, age, role }
|
|
377
|
+
|
|
378
|
+
# File serving
|
|
379
|
+
get '/css/*' -> @send "public/#{@req.path.slice(5)}"
|
|
380
|
+
notFound -> @send 'index.html', 'text/html; charset=UTF-8'
|
|
381
|
+
|
|
382
|
+
# Middleware
|
|
383
|
+
import { cors, logger, sessions } from '@rip-lang/api/middleware'
|
|
384
|
+
|
|
385
|
+
use logger()
|
|
386
|
+
use cors origin: '*'
|
|
387
|
+
use sessions secret: process.env.SECRET
|
|
388
|
+
|
|
389
|
+
# Lifecycle hooks
|
|
390
|
+
before -> @start = Date.now()
|
|
391
|
+
after -> console.log "#{@req.method} #{@req.path} - #{Date.now() - @start}ms"
|
|
392
|
+
|
|
393
|
+
start port: 3000
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
#### read() Validators
|
|
397
|
+
|
|
398
|
+
```coffee
|
|
399
|
+
id = read 'id', 'id!' # positive integer (required)
|
|
400
|
+
count = read 'count', 'whole' # non-negative integer
|
|
401
|
+
price = read 'price', 'money' # cents (multiplies by 100)
|
|
402
|
+
name = read 'name', 'string' # collapses whitespace
|
|
403
|
+
email = read 'email', 'email' # valid email format
|
|
404
|
+
phone = read 'phone', 'phone' # US phone → (555) 123-4567
|
|
405
|
+
state = read 'state', 'state' # two-letter → uppercase
|
|
406
|
+
zip = read 'zip', 'zip' # 5-digit zip
|
|
407
|
+
url = read 'url', 'url' # valid URL
|
|
408
|
+
uuid = read 'id', 'uuid' # UUID format
|
|
409
|
+
date = read 'date', 'date' # YYYY-MM-DD
|
|
410
|
+
time = read 'time', 'time' # HH:MM or HH:MM:SS
|
|
411
|
+
flag = read 'flag', 'bool' # boolean
|
|
412
|
+
tags = read 'tags', 'array' # must be array
|
|
413
|
+
ids = read 'ids', 'ids' # "1,2,3" → [1, 2, 3]
|
|
414
|
+
slug = read 'slug', 'slug' # URL-safe slug
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### @rip-lang/server — Production Server
|
|
418
|
+
|
|
419
|
+
Multi-worker process manager with hot reload, HTTPS, and mDNS.
|
|
420
|
+
|
|
421
|
+
```bash
|
|
422
|
+
rip-server # Start (uses ./index.rip)
|
|
423
|
+
rip-server -w # With file watching + hot-reload
|
|
424
|
+
rip-server myapp # Named (accessible at myapp.local)
|
|
425
|
+
rip-server http:3000 # HTTP on specific port
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
### @rip-lang/db — DuckDB Server
|
|
429
|
+
|
|
430
|
+
HTTP server for DuckDB with JSONCompact responses.
|
|
431
|
+
|
|
432
|
+
```bash
|
|
433
|
+
rip-db mydata.duckdb --port=4000
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
```coffee
|
|
437
|
+
# Query from Rip
|
|
438
|
+
import { get, start } from '@rip-lang/api'
|
|
439
|
+
import { DB } from '@rip-lang/db'
|
|
440
|
+
|
|
441
|
+
db = DB.new 'data.duckdb'
|
|
442
|
+
|
|
443
|
+
get '/users' -> db.query! "SELECT * FROM users"
|
|
444
|
+
get '/users/:id' -> db.query! "SELECT * FROM users WHERE id = ?", [read 'id', 'id!']
|
|
445
|
+
|
|
446
|
+
start port: 3000
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
### @rip-lang/ui — Reactive Web Framework
|
|
450
|
+
|
|
451
|
+
Zero-build framework. Ships the 40KB compiler to the browser and compiles `.rip` components on demand.
|
|
452
|
+
|
|
453
|
+
```coffee
|
|
454
|
+
# Server setup (index.rip)
|
|
455
|
+
import { get, use, start, notFound } from '@rip-lang/api'
|
|
456
|
+
import { ripUI } from '@rip-lang/ui/serve'
|
|
457
|
+
|
|
458
|
+
dir = import.meta.dir
|
|
459
|
+
use ripUI pages: "#{dir}/pages", watch: true
|
|
460
|
+
notFound -> @send "#{dir}/index.html", 'text/html; charset=UTF-8'
|
|
461
|
+
start port: 3000
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
```coffee
|
|
465
|
+
# Component (pages/counter.rip)
|
|
466
|
+
Counter = component
|
|
467
|
+
@count := 0
|
|
468
|
+
doubled ~= @count * 2
|
|
469
|
+
|
|
470
|
+
increment: -> @count += 1
|
|
471
|
+
|
|
472
|
+
render
|
|
473
|
+
div.counter
|
|
474
|
+
h1 "Count: #{@count}"
|
|
475
|
+
p "Doubled: #{doubled}"
|
|
476
|
+
button @click: @increment, "+"
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
### @rip-lang/swarm — Parallel Job Runner
|
|
480
|
+
|
|
481
|
+
```coffee
|
|
482
|
+
import { swarm, init, retry, todo } from '@rip-lang/swarm'
|
|
483
|
+
|
|
484
|
+
setup = ->
|
|
485
|
+
unless retry()
|
|
486
|
+
init()
|
|
487
|
+
for i in [1..100] then todo(i)
|
|
488
|
+
|
|
489
|
+
perform = (task, ctx) ->
|
|
490
|
+
await Bun.sleep(Math.random() * 1000)
|
|
491
|
+
|
|
492
|
+
swarm { setup, perform }
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
### @rip-lang/csv — CSV Parser + Writer
|
|
496
|
+
|
|
497
|
+
```coffee
|
|
498
|
+
import { CSV } from '@rip-lang/csv'
|
|
499
|
+
|
|
500
|
+
# Parse
|
|
501
|
+
rows = CSV.read "name,age\nAlice,30\nBob,25\n", headers: true
|
|
502
|
+
# [{name: 'Alice', age: '30'}, {name: 'Bob', age: '25'}]
|
|
503
|
+
|
|
504
|
+
# Write
|
|
505
|
+
CSV.save! 'output.csv', rows
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
### @rip-lang/schema — ORM + Validation
|
|
509
|
+
|
|
510
|
+
```coffee
|
|
511
|
+
import { Model } from '@rip-lang/schema'
|
|
512
|
+
|
|
513
|
+
class User extends Model
|
|
514
|
+
@table = 'users'
|
|
515
|
+
@schema
|
|
516
|
+
name: { type: 'string', required: true }
|
|
517
|
+
email: { type: 'email', unique: true }
|
|
518
|
+
|
|
519
|
+
user = User.find!(25)
|
|
520
|
+
user.name = 'Alice'
|
|
521
|
+
user.save!()
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
---
|
|
525
|
+
|
|
526
|
+
## Full-Stack Example
|
|
527
|
+
|
|
528
|
+
A complete API server in Rip:
|
|
529
|
+
|
|
530
|
+
```coffee
|
|
531
|
+
import { get, post, use, read, start, notFound } from '@rip-lang/api'
|
|
532
|
+
import { cors, logger } from '@rip-lang/api/middleware'
|
|
533
|
+
|
|
534
|
+
use logger()
|
|
535
|
+
use cors origin: '*'
|
|
536
|
+
|
|
537
|
+
# In-memory store
|
|
538
|
+
users = []
|
|
539
|
+
nextId = 1
|
|
540
|
+
|
|
541
|
+
get '/api/users' -> users
|
|
542
|
+
|
|
543
|
+
get '/api/users/:id' ->
|
|
544
|
+
id = read 'id', 'id!'
|
|
545
|
+
user = users.find (u) -> u.id is id
|
|
546
|
+
user or throw { status: 404, message: 'Not found' }
|
|
547
|
+
|
|
548
|
+
post '/api/users' ->
|
|
549
|
+
name = read 'name', 'string!'
|
|
550
|
+
email = read 'email', 'email!'
|
|
551
|
+
user = { id: nextId++, name, email }
|
|
552
|
+
users.push user
|
|
553
|
+
user
|
|
554
|
+
|
|
555
|
+
notFound -> { error: 'Not found' }
|
|
556
|
+
|
|
557
|
+
start port: 3000
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
---
|
|
561
|
+
|
|
562
|
+
## CLI Reference
|
|
563
|
+
|
|
564
|
+
```bash
|
|
565
|
+
rip # REPL
|
|
566
|
+
rip file.rip # Run
|
|
567
|
+
rip -c file.rip # Compile to JS (stdout)
|
|
568
|
+
rip -o out.js file.rip # Compile to file
|
|
569
|
+
rip -m file.rip # Compile with inline source map
|
|
570
|
+
rip -d file.rip # Generate .d.ts
|
|
571
|
+
rip -t file.rip # Show tokens
|
|
572
|
+
rip -s file.rip # Show S-expressions
|
|
573
|
+
rip -q -c file.rip # Quiet (no headers)
|
|
574
|
+
bun run test # Run test suite (1,140 tests)
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
---
|
|
578
|
+
|
|
579
|
+
## Documentation
|
|
580
|
+
|
|
581
|
+
| Document | Audience | Purpose |
|
|
582
|
+
|----------|----------|---------|
|
|
583
|
+
| **[RIP-GUIDE.md](RIP-GUIDE.md)** | Users / AI | Practical guide for using Rip in projects |
|
|
584
|
+
| **[AGENT.md](../AGENT.md)** | AI agents | Get up to speed for working on the compiler |
|
|
585
|
+
| **[RIP-LANG.md](RIP-LANG.md)** | Users | Full language reference |
|
|
586
|
+
| **[RIP-TYPES.md](RIP-TYPES.md)** | Contributors | Type system specification |
|
|
587
|
+
| **[RIP-REACTIVITY.md](RIP-REACTIVITY.md)** | Users | Reactivity deep dive |
|
|
588
|
+
| **[RIP-INTERNALS.md](RIP-INTERNALS.md)** | Contributors | Compiler architecture |
|
|
589
|
+
|
|
590
|
+
## Resources
|
|
591
|
+
|
|
592
|
+
- [Rip Playground](https://shreeve.github.io/rip-lang/) — Try Rip in the browser
|
|
593
|
+
- [VS Code Extension](https://marketplace.visualstudio.com/items?itemName=rip-lang.rip) — IDE support
|
|
594
|
+
- [GitHub](https://github.com/shreeve/rip-lang) — Source code
|
|
595
|
+
|
|
596
|
+
---
|
|
597
|
+
|
|
598
|
+
*Rip — Start simple. Build incrementally. Ship elegantly.*
|
package/docs/RIP-INTERNALS.md
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
<img src="https://raw.githubusercontent.com/shreeve/rip-lang/main/docs/rip.png" style="width:50px" /> <br>
|
|
2
|
+
|
|
3
|
+
# Rip Internals
|
|
2
4
|
|
|
3
5
|
> Architecture, design decisions, and technical reference for the Rip compiler.
|
|
4
6
|
|
|
@@ -43,7 +45,7 @@ class BinaryOp {
|
|
|
43
45
|
["+", left, right] // That's it!
|
|
44
46
|
```
|
|
45
47
|
|
|
46
|
-
**Result:** CoffeeScript's compiler is 17,760 LOC. Rip's is ~
|
|
48
|
+
**Result:** CoffeeScript's compiler is 17,760 LOC. Rip's is ~10,300 LOC — smaller, yet includes a complete reactive runtime, type system, component system, and source maps.
|
|
47
49
|
|
|
48
50
|
> **Transform the IR (s-expressions), not the output (strings).**
|
|
49
51
|
|
|
@@ -75,7 +77,7 @@ console.log(code);
|
|
|
75
77
|
| Dependencies | Multiple | **Zero** |
|
|
76
78
|
| Parser generator | External (Jison) | **Built-in (Solar)** |
|
|
77
79
|
| Self-hosting | No | **Yes** |
|
|
78
|
-
| Total LOC | 17,760 | ~
|
|
80
|
+
| Total LOC | 17,760 | ~10,300 |
|
|
79
81
|
|
|
80
82
|
## Design Principles
|
|
81
83
|
|
|
@@ -91,7 +93,7 @@ console.log(code);
|
|
|
91
93
|
|
|
92
94
|
```
|
|
93
95
|
Source Code → Lexer → emitTypes → Parser → S-Expressions → Codegen → JavaScript
|
|
94
|
-
(1,
|
|
96
|
+
(1,867) (types.js) (357) (arrays + .loc) (3,292) + source map
|
|
95
97
|
↓
|
|
96
98
|
file.d.ts (when types: "emit")
|
|
97
99
|
```
|
|
@@ -100,13 +102,15 @@ Source Code → Lexer → emitTypes → Parser → S-Expressions → C
|
|
|
100
102
|
|
|
101
103
|
| File | Purpose | Lines | Modify? |
|
|
102
104
|
|------|---------|-------|---------|
|
|
103
|
-
| `src/lexer.js` | Lexer + Rewriter | 1,
|
|
104
|
-
| `src/compiler.js` | Compiler + Code Generator | 3,
|
|
105
|
-
| `src/types.js` | Type System (lexer sidecar) |
|
|
106
|
-
| `src/components.js` | Component System (compiler sidecar) |
|
|
107
|
-
| `src/
|
|
108
|
-
| `src/
|
|
109
|
-
| `src/
|
|
105
|
+
| `src/lexer.js` | Lexer + Rewriter | 1,867 | Yes |
|
|
106
|
+
| `src/compiler.js` | Compiler + Code Generator | 3,292 | Yes |
|
|
107
|
+
| `src/types.js` | Type System (lexer sidecar) | 719 | Yes |
|
|
108
|
+
| `src/components.js` | Component System (compiler sidecar) | 1,240 | Yes |
|
|
109
|
+
| `src/sourcemaps.js` | Source Map V3 Generator | 122 | Yes |
|
|
110
|
+
| `src/tags.js` | HTML Tag Classification | 63 | Yes |
|
|
111
|
+
| `src/parser.js` | Generated parser | 357 | No (auto-gen) |
|
|
112
|
+
| `src/grammar/grammar.rip` | Grammar specification | 935 | Yes (carefully) |
|
|
113
|
+
| `src/grammar/solar.rip` | Parser generator | 916 | No |
|
|
110
114
|
|
|
111
115
|
## Example Flow
|
|
112
116
|
|
|
@@ -271,7 +275,7 @@ S-expressions are simple arrays that serve as Rip's intermediate representation
|
|
|
271
275
|
|
|
272
276
|
# 4. Lexer & Rewriter
|
|
273
277
|
|
|
274
|
-
The lexer (`src/lexer.js`) is a clean reimplementation that replaces the old lexer (3,260 lines) with ~1,
|
|
278
|
+
The lexer (`src/lexer.js`) is a clean reimplementation that replaces the old lexer (3,260 lines) with ~1,870 lines producing the same token vocabulary the parser expects.
|
|
275
279
|
|
|
276
280
|
## Architecture
|
|
277
281
|
|
|
@@ -435,7 +439,7 @@ REGEX tokens store `delimiter` and optional `heregex` flags in `token.data`.
|
|
|
435
439
|
|
|
436
440
|
# 6. Compiler
|
|
437
441
|
|
|
438
|
-
The compiler (`src/compiler.js`) is a clean reimplementation replacing the old compiler (6,016 lines) with ~3,
|
|
442
|
+
The compiler (`src/compiler.js`) is a clean reimplementation replacing the old compiler (6,016 lines) with ~3,290 lines producing identical JavaScript output.
|
|
439
443
|
|
|
440
444
|
## Structure
|
|
441
445
|
|
|
@@ -479,7 +483,7 @@ The `Compiler` class's lexer adapter reconstructs `new String()` wrapping from t
|
|
|
479
483
|
|
|
480
484
|
| Area | Old lines | New lines | Reduction |
|
|
481
485
|
|------|-----------|-----------|-----------|
|
|
482
|
-
| Total file | 6,016 | ~3,
|
|
486
|
+
| Total file | 6,016 | ~3,290 | **45%** |
|
|
483
487
|
| Body generation | ~500 | ~200 | 60% |
|
|
484
488
|
| Variable collection | ~230 | ~100 | 57% |
|
|
485
489
|
| Helper methods | ~600 | ~250 | 58% |
|
|
@@ -490,7 +494,7 @@ The `Compiler` class's lexer adapter reconstructs `new String()` wrapping from t
|
|
|
490
494
|
|
|
491
495
|
**Solar** is a complete SLR(1) parser generator included with Rip — written in Rip, compiled by Rip, zero external dependencies.
|
|
492
496
|
|
|
493
|
-
**Location:** `src/grammar/solar.rip` (
|
|
497
|
+
**Location:** `src/grammar/solar.rip` (916 lines)
|
|
494
498
|
|
|
495
499
|
## Grammar Syntax
|
|
496
500
|
|
|
@@ -529,7 +533,7 @@ Parenthetical: [
|
|
|
529
533
|
| Parse time | 12,500ms | ~50ms |
|
|
530
534
|
| Dependencies | Many | Zero |
|
|
531
535
|
| Self-hosting | No | Yes |
|
|
532
|
-
| Code size | 2,285 LOC |
|
|
536
|
+
| Code size | 2,285 LOC | 916 LOC |
|
|
533
537
|
|
|
534
538
|
After modifying `src/grammar/grammar.rip`:
|
|
535
539
|
|
|
@@ -558,27 +562,21 @@ rip> .sexp # Toggle s-expression display
|
|
|
558
562
|
rip> .js # Toggle JS display
|
|
559
563
|
```
|
|
560
564
|
|
|
561
|
-
Compare old and new compilers across all test suites:
|
|
562
|
-
|
|
563
|
-
```bash
|
|
564
|
-
bun src/compare-compilers.js
|
|
565
|
-
```
|
|
566
|
-
|
|
567
565
|
---
|
|
568
566
|
|
|
569
567
|
# 9. Future Work
|
|
570
568
|
|
|
571
|
-
- Comment preservation for source maps
|
|
572
569
|
- Parser update to read `.data` directly instead of `new String()` properties
|
|
573
570
|
- Once parser supports `.data`, the `meta()`/`str()` helpers become trivial to update
|
|
574
571
|
|
|
575
572
|
---
|
|
576
573
|
|
|
577
574
|
**See Also:**
|
|
575
|
+
- [RIP-GUIDE.md](RIP-GUIDE.md) — Practical guide for using Rip
|
|
578
576
|
- [RIP-LANG.md](RIP-LANG.md) — Language reference
|
|
579
577
|
- [RIP-TYPES.md](RIP-TYPES.md) — Type system specification
|
|
580
578
|
- [RIP-REACTIVITY.md](RIP-REACTIVITY.md) — Reactivity deep dive
|
|
581
579
|
|
|
582
580
|
---
|
|
583
581
|
|
|
584
|
-
*Rip 3.
|
|
582
|
+
*Rip 3.4 — 1,140 tests passing — Zero dependencies — Self-hosting — ~10,300 LOC*
|
package/docs/RIP-LANG.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
<img src="https://raw.githubusercontent.com/shreeve/rip-lang/main/docs/rip.png" style="width:50px" /> <br>
|
|
2
|
+
|
|
1
3
|
# Rip 3.0 Language Reference
|
|
2
4
|
|
|
3
|
-
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, ~
|
|
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,300 LOC.
|
|
4
6
|
|
|
5
7
|
---
|
|
6
8
|
|
|
@@ -1163,4 +1165,4 @@ count = 10 # Logs: "Count: 10, Doubled: 20"
|
|
|
1163
1165
|
|
|
1164
1166
|
---
|
|
1165
1167
|
|
|
1166
|
-
*Rip 3.
|
|
1168
|
+
*Rip 3.4 — 1,140 tests passing — Zero dependencies — Self-hosting — ~10,300 LOC*
|
package/docs/RIP-REACTIVITY.md
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
<img src="https://raw.githubusercontent.com/shreeve/rip-lang/main/docs/rip.png" style="width:50px" /> <br>
|
|
2
|
+
|
|
3
|
+
# Rip Reactivity
|
|
2
4
|
|
|
3
5
|
Rip implements a **fine-grained reactive system** that rivals and often exceeds the capabilities of major frameworks like Vue, Svelte, Solid, and React — in just ~200 lines of runtime code.
|
|
4
6
|
|
package/docs/RIP-TYPES.md
CHANGED
package/docs/dist/rip.browser.js
CHANGED
|
@@ -3983,7 +3983,7 @@ if (typeof globalThis !== 'undefined') {
|
|
|
3983
3983
|
};
|
|
3984
3984
|
}
|
|
3985
3985
|
|
|
3986
|
-
// src/
|
|
3986
|
+
// src/sourcemaps.js
|
|
3987
3987
|
var B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
3988
3988
|
function vlqEncode(value) {
|
|
3989
3989
|
let result = "";
|
|
@@ -7536,8 +7536,8 @@ function getComponentRuntime() {
|
|
|
7536
7536
|
return new CodeGenerator({}).getComponentRuntime();
|
|
7537
7537
|
}
|
|
7538
7538
|
// src/browser.js
|
|
7539
|
-
var VERSION = "3.4.
|
|
7540
|
-
var BUILD_DATE = "2026-02-09@
|
|
7539
|
+
var VERSION = "3.4.5";
|
|
7540
|
+
var BUILD_DATE = "2026-02-09@14:23:53GMT";
|
|
7541
7541
|
var dedent = (s) => {
|
|
7542
7542
|
const m = s.match(/^[ \t]*(?=\S)/gm);
|
|
7543
7543
|
const i = Math.min(...(m || []).map((x) => x.length));
|
|
@@ -525,4 +525,4 @@ if (typeof globalThis !== 'undefined') {
|
|
|
525
525
|
`:"",Y=U.slice(0,W).join(`
|
|
526
526
|
`)}let $=new v().tokenize(Y);if(this.options.showTokens)$.forEach((R)=>console.log(`${R[0].padEnd(12)} ${JSON.stringify(R[1])}`)),console.log();let Q=null;if(this.options.types==="emit"||this.options.types==="check"||this.options.types===!0)Q=Q1($);$=$.filter((R)=>R[0]!=="TYPE_DECL");while($.length>0&&$[0][0]==="TERMINATOR")$.shift();if($.every((R)=>R[0]==="TERMINATOR"))return{tokens:$,sexpr:["program"],code:"",dts:Q,data:F,reactiveVars:{}};g.lexer={tokens:$,pos:0,setInput:function(){},lex:function(){if(this.pos>=this.tokens.length)return 1;let R=this.tokens[this.pos++],M=R[1];if(R.data)M=new String(M),Object.assign(M,R.data);return this.text=M,this.loc=R.loc,R[0]}};let D;try{D=g.parse(Y)}catch(R){if(/\?\s*\([^)]*\?[^)]*:[^)]*\)\s*:/.test(Y)||/\?\s+\w+\s+\?\s+/.test(Y))throw Error("Nested ternary operators are not supported. Use if/else statements instead.");throw R}if(this.options.showSExpr)console.log(y(D,0,!0)),console.log();let Z=null;if(this.options.sourceMap){let R=(this.options.filename||"output")+".js",M=this.options.filename||"input.rip";Z=new Y1(R,M,Y)}let X=new V({dataSection:F,skipReactiveRuntime:this.options.skipReactiveRuntime,skipComponentRuntime:this.options.skipComponentRuntime,reactiveVars:this.options.reactiveVars,sourceMap:Z}),J=X.compile(D),K=Z?Z.toJSON():null,z=Z?Z.toReverseMap():null;if(K&&this.options.sourceMap==="inline"){let R=typeof Buffer<"u"?Buffer.from(K).toString("base64"):btoa(K);J+=`
|
|
527
527
|
//# sourceMappingURL=data:application/json;base64,${R}`}else if(K&&this.options.filename)J+=`
|
|
528
|
-
//# sourceMappingURL=${this.options.filename}.js.map`;return{tokens:$,sexpr:D,code:J,dts:Q,map:K,reverseMap:z,data:F,reactiveVars:X.reactiveVars}}compileToJS(Y){return this.compile(Y).code}compileToSExpr(Y){return this.compile(Y).sexpr}}E1(V);V.prototype.generateEnum=Z1;function H2(Y,F={}){return new r(F).compile(Y)}function t(Y,F={}){return new r(F).compileToJS(Y)}function O2(){return new V({}).getReactiveRuntime()}function q2(){return new V({}).getComponentRuntime()}var v2="3.4.
|
|
528
|
+
//# sourceMappingURL=${this.options.filename}.js.map`;return{tokens:$,sexpr:D,code:J,dts:Q,map:K,reverseMap:z,data:F,reactiveVars:X.reactiveVars}}compileToJS(Y){return this.compile(Y).code}compileToSExpr(Y){return this.compile(Y).sexpr}}E1(V);V.prototype.generateEnum=Z1;function H2(Y,F={}){return new r(F).compile(Y)}function t(Y,F={}){return new r(F).compileToJS(Y)}function O2(){return new V({}).getReactiveRuntime()}function q2(){return new V({}).getComponentRuntime()}var v2="3.4.5",g2="2026-02-09@14:23:53GMT",P2=(Y)=>{let F=Y.match(/^[ \t]*(?=\S)/gm),U=Math.min(...(F||[]).map((W)=>W.length));return Y.replace(RegExp(`^[ ]{${U}}`,"gm"),"").trim()};async function T1(){let Y=document.querySelectorAll('script[type="text/rip"]');for(let F of Y){if(F.hasAttribute("data-rip-processed"))continue;try{let U=P2(F.textContent),W=t(U);(0,eval)(W),F.setAttribute("data-rip-processed","true")}catch(U){console.error("Error compiling Rip script:",U),console.error("Script content:",F.textContent)}}}if(typeof document<"u")if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",T1);else T1();function _2(Y){try{let U=t(Y).replace(/^let\s+[^;]+;\s*\n\s*/m,"");U=U.replace(/^const\s+/gm,"var ");let W=(0,eval)(U);if(W!==void 0)globalThis._=W;return W}catch(F){console.error("Rip compilation error:",F.message);return}}if(typeof globalThis<"u")globalThis.rip=_2;export{_2 as rip,T1 as processRipScripts,g as parser,O2 as getReactiveRuntime,q2 as getComponentRuntime,y as formatSExpr,t as compileToJS,H2 as compile,v2 as VERSION,v as Lexer,r as Compiler,V as CodeGenerator,g2 as BUILD_DATE};
|
|
Binary file
|
package/docs/index.html
CHANGED
|
@@ -191,7 +191,7 @@
|
|
|
191
191
|
display: flex;
|
|
192
192
|
align-items: flex-start;
|
|
193
193
|
gap: 4px;
|
|
194
|
-
background: #
|
|
194
|
+
background: #2d2d30;
|
|
195
195
|
padding: 8px 10px;
|
|
196
196
|
border: 1px solid #3e3e42;
|
|
197
197
|
border-radius: 4px;
|
|
@@ -907,9 +907,13 @@ console.log "Domain:", domain`;
|
|
|
907
907
|
modeToggle.addEventListener('click', () => {
|
|
908
908
|
isDark = !isDark;
|
|
909
909
|
applyMode();
|
|
910
|
-
|
|
911
|
-
themeSelect.value
|
|
912
|
-
|
|
910
|
+
// Only reset theme if currently using a built-in default
|
|
911
|
+
const current = themeSelect.value;
|
|
912
|
+
if (current === 'vs' || current === 'vs-dark') {
|
|
913
|
+
const defaultTheme = isDark ? 'vs-dark' : 'vs';
|
|
914
|
+
themeSelect.value = defaultTheme;
|
|
915
|
+
setTheme(defaultTheme);
|
|
916
|
+
}
|
|
913
917
|
});
|
|
914
918
|
|
|
915
919
|
// ========================================================================
|
|
@@ -1038,7 +1042,7 @@ console.log "Domain:", domain`;
|
|
|
1038
1042
|
// Tab Switching
|
|
1039
1043
|
// ========================================================================
|
|
1040
1044
|
|
|
1041
|
-
function switchToTab(tabName) {
|
|
1045
|
+
async function switchToTab(tabName) {
|
|
1042
1046
|
// Update URL hash
|
|
1043
1047
|
window.location.hash = tabName;
|
|
1044
1048
|
|
|
@@ -1057,7 +1061,7 @@ console.log "Domain:", domain`;
|
|
|
1057
1061
|
replEditor.focus();
|
|
1058
1062
|
} else if (tabName === 'compiler') {
|
|
1059
1063
|
// Compiler restores user's selected theme
|
|
1060
|
-
setTheme(themeSelect.value);
|
|
1064
|
+
await setTheme(themeSelect.value);
|
|
1061
1065
|
compileCode();
|
|
1062
1066
|
}
|
|
1063
1067
|
}
|
|
@@ -1083,7 +1087,7 @@ console.log "Domain:", domain`;
|
|
|
1083
1087
|
// Initialize from URL hash on page load (default to compiler)
|
|
1084
1088
|
const initialTab = window.location.hash.slice(1);
|
|
1085
1089
|
if (['compiler', 'repl'].includes(initialTab)) {
|
|
1086
|
-
switchToTab(initialTab);
|
|
1090
|
+
await switchToTab(initialTab);
|
|
1087
1091
|
} else {
|
|
1088
1092
|
// No hash - compiler is default
|
|
1089
1093
|
compileCode();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rip-lang",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.5",
|
|
4
4
|
"description": "A modern language that compiles to JavaScript",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/compiler.js",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"author": "Steve Shreeve <steve.shreeve@gmail.com>",
|
|
68
68
|
"license": "MIT",
|
|
69
69
|
"devDependencies": {
|
|
70
|
-
"@rip-lang/api": "1.1.
|
|
71
|
-
"@rip-lang/ui": "0.1.
|
|
70
|
+
"@rip-lang/api": "1.1.5",
|
|
71
|
+
"@rip-lang/ui": "0.1.3"
|
|
72
72
|
}
|
|
73
73
|
}
|
package/src/compiler.js
CHANGED
|
@@ -12,7 +12,7 @@ import { Lexer } from './lexer.js';
|
|
|
12
12
|
import { parser } from './parser.js';
|
|
13
13
|
import { installComponentSupport } from './components.js';
|
|
14
14
|
import { emitTypes, generateEnum } from './types.js';
|
|
15
|
-
import { SourceMapGenerator } from './
|
|
15
|
+
import { SourceMapGenerator } from './sourcemaps.js';
|
|
16
16
|
|
|
17
17
|
// =============================================================================
|
|
18
18
|
// Metadata helpers — isolate all new String() awareness here
|
package/docs/example.html
DELETED
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>Inline Rip Code Demo</title>
|
|
7
|
-
<style>
|
|
8
|
-
* {
|
|
9
|
-
margin: 0;
|
|
10
|
-
padding: 0;
|
|
11
|
-
box-sizing: border-box;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
body {
|
|
15
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
16
|
-
padding: 40px;
|
|
17
|
-
max-width: 800px;
|
|
18
|
-
margin: 0 auto;
|
|
19
|
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
20
|
-
min-height: 100vh;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
.container {
|
|
24
|
-
background: white;
|
|
25
|
-
border-radius: 12px;
|
|
26
|
-
padding: 40px;
|
|
27
|
-
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
h1 {
|
|
31
|
-
color: #333;
|
|
32
|
-
margin-bottom: 10px;
|
|
33
|
-
font-size: 32px;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
.subtitle {
|
|
37
|
-
color: #666;
|
|
38
|
-
margin-bottom: 30px;
|
|
39
|
-
font-size: 16px;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
.output {
|
|
43
|
-
background: #f8f9fa;
|
|
44
|
-
border-left: 4px solid #667eea;
|
|
45
|
-
padding: 20px;
|
|
46
|
-
margin: 20px 0;
|
|
47
|
-
border-radius: 4px;
|
|
48
|
-
font-family: 'Monaco', 'Menlo', monospace;
|
|
49
|
-
font-size: 14px;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
.output h3 {
|
|
53
|
-
margin: 0 0 10px 0;
|
|
54
|
-
color: #667eea;
|
|
55
|
-
font-size: 14px;
|
|
56
|
-
text-transform: uppercase;
|
|
57
|
-
letter-spacing: 1px;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
button {
|
|
61
|
-
background: #667eea;
|
|
62
|
-
color: white;
|
|
63
|
-
border: none;
|
|
64
|
-
padding: 12px 24px;
|
|
65
|
-
border-radius: 6px;
|
|
66
|
-
cursor: pointer;
|
|
67
|
-
font-size: 16px;
|
|
68
|
-
font-weight: 600;
|
|
69
|
-
margin: 10px 10px 10px 0;
|
|
70
|
-
transition: all 0.3s;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
button:hover {
|
|
74
|
-
background: #5568d3;
|
|
75
|
-
transform: translateY(-2px);
|
|
76
|
-
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
.code-block {
|
|
80
|
-
background: #282c34;
|
|
81
|
-
color: #abb2bf;
|
|
82
|
-
padding: 20px;
|
|
83
|
-
border-radius: 8px;
|
|
84
|
-
margin: 20px 0;
|
|
85
|
-
overflow-x: auto;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
.code-block pre {
|
|
89
|
-
margin: 0;
|
|
90
|
-
font-family: 'Monaco', 'Menlo', monospace;
|
|
91
|
-
font-size: 13px;
|
|
92
|
-
line-height: 1.5;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
.keyword { color: #c678dd; }
|
|
96
|
-
.function { color: #61afef; }
|
|
97
|
-
.string { color: #98c379; }
|
|
98
|
-
.comment { color: #5c6370; font-style: italic; }
|
|
99
|
-
</style>
|
|
100
|
-
</head>
|
|
101
|
-
<body>
|
|
102
|
-
<div class="container">
|
|
103
|
-
<h1>🔮 Inline Rip Demo</h1>
|
|
104
|
-
<p class="subtitle">Rip code embedded directly in HTML with <code><script type="text/rip"></code></p>
|
|
105
|
-
|
|
106
|
-
<div class="code-block">
|
|
107
|
-
<pre><span class="comment"># This Rip code runs automatically!</span>
|
|
108
|
-
<span class="keyword">def</span> <span class="function">greet</span>(name)
|
|
109
|
-
<span class="string">"Hello, ${name}! 👋"</span>
|
|
110
|
-
|
|
111
|
-
<span class="keyword">def</span> <span class="function">fibonacci</span>(n)
|
|
112
|
-
<span class="keyword">if</span> n <= 1
|
|
113
|
-
n
|
|
114
|
-
<span class="keyword">else</span>
|
|
115
|
-
fibonacci(n - 1) + fibonacci(n - 2)</pre>
|
|
116
|
-
</div>
|
|
117
|
-
|
|
118
|
-
<div id="output" class="output">
|
|
119
|
-
<h3>Output</h3>
|
|
120
|
-
<div id="results">Click the buttons to see Rip in action!</div>
|
|
121
|
-
</div>
|
|
122
|
-
|
|
123
|
-
<button onclick="testGreeting()">Test Greeting</button>
|
|
124
|
-
<button onclick="testFibonacci()">Calculate Fibonacci(10)</button>
|
|
125
|
-
<button onclick="testMath()">Test Math Operations</button>
|
|
126
|
-
</div>
|
|
127
|
-
|
|
128
|
-
<!-- Rip code embedded directly in the page! -->
|
|
129
|
-
<script type="text/rip">
|
|
130
|
-
# Define greeting function
|
|
131
|
-
def greet(name)
|
|
132
|
-
"Hello, ${name}! 👋"
|
|
133
|
-
|
|
134
|
-
# Define fibonacci function
|
|
135
|
-
def fibonacci(n)
|
|
136
|
-
if n <= 1
|
|
137
|
-
n
|
|
138
|
-
else
|
|
139
|
-
fibonacci(n - 1) + fibonacci(n - 2)
|
|
140
|
-
|
|
141
|
-
# Define math operations
|
|
142
|
-
def calculate(a, b)
|
|
143
|
-
sum = a + b
|
|
144
|
-
product = a * b
|
|
145
|
-
power = a ** b
|
|
146
|
-
{sum: sum, product: product, power: power}
|
|
147
|
-
</script>
|
|
148
|
-
|
|
149
|
-
<!-- Load the Rip compiler and runtime (loads after Rip scripts are in DOM) -->
|
|
150
|
-
<script type="module" src="./dist/rip.browser.min.js"></script>
|
|
151
|
-
|
|
152
|
-
<!-- Regular JavaScript to handle UI interactions -->
|
|
153
|
-
<script>
|
|
154
|
-
const output = document.getElementById('results');
|
|
155
|
-
|
|
156
|
-
function testGreeting() {
|
|
157
|
-
const result = greet('Rip User');
|
|
158
|
-
output.innerHTML = `<strong>greet('Rip User')</strong><br>→ ${result}`;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function testFibonacci() {
|
|
162
|
-
const result = fibonacci(10);
|
|
163
|
-
output.innerHTML = `<strong>fibonacci(10)</strong><br>→ ${result}`;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function testMath() {
|
|
167
|
-
const result = calculate(2, 3);
|
|
168
|
-
output.innerHTML = `
|
|
169
|
-
<strong>calculate(2, 3)</strong><br>
|
|
170
|
-
→ sum: ${result.sum}<br>
|
|
171
|
-
→ product: ${result.product}<br>
|
|
172
|
-
→ power: ${result.power}
|
|
173
|
-
`;
|
|
174
|
-
}
|
|
175
|
-
</script>
|
|
176
|
-
</body>
|
|
177
|
-
</html>
|
|
File without changes
|