rip-lang 3.7.3 → 3.8.8
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 +111 -0
- package/README.md +42 -34
- package/docs/RIP-INTERNALS.md +2 -4
- package/docs/RIP-LANG.md +150 -3
- package/docs/RIP-TYPES.md +1 -2
- package/docs/demo.html +342 -0
- package/docs/dist/rip-ui.min.js +516 -0
- package/docs/dist/rip-ui.min.js.br +0 -0
- package/docs/dist/rip.browser.js +379 -461
- package/docs/dist/rip.browser.min.js +204 -220
- package/docs/dist/rip.browser.min.js.br +0 -0
- package/docs/dist/ui.js +956 -0
- package/docs/dist/ui.min.js +2 -0
- package/docs/dist/ui.min.js.br +0 -0
- package/docs/dist/ui.rip +957 -0
- package/docs/dist/ui.rip.br +0 -0
- package/docs/examples.rip +180 -0
- package/docs/index.html +3 -1599
- package/docs/playground-app.html +1022 -0
- package/docs/playground-js.html +1645 -0
- package/docs/playground-rip-ui.html +1419 -0
- package/docs/playground-rip.html +1450 -0
- package/docs/rip-fav.svg +5 -0
- package/package.json +3 -3
- package/scripts/serve.js +3 -2
- package/src/browser.js +38 -16
- package/src/compiler.js +165 -226
- package/src/components.js +153 -140
- package/src/grammar/README.md +234 -0
- package/src/grammar/lunar.rip +2412 -0
- package/src/grammar/solar.rip +18 -4
- package/src/lexer.js +82 -30
- package/src/parser-rd.js +3242 -0
- package/src/parser.js +6 -5
- package/src/repl.js +24 -5
- package/docs/NOTES.md +0 -93
- package/docs/RIP-GUIDE.md +0 -698
- package/docs/RIP-REACTIVITY.md +0 -311
package/docs/RIP-GUIDE.md
DELETED
|
@@ -1,698 +0,0 @@
|
|
|
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
|
-
label = "big" if x > 5 else "small"
|
|
121
|
-
|
|
122
|
-
# Postfix
|
|
123
|
-
console.log "hi" if ready
|
|
124
|
-
return unless valid
|
|
125
|
-
|
|
126
|
-
# Switch
|
|
127
|
-
result = switch status
|
|
128
|
-
when "active" then "Running"
|
|
129
|
-
when "done" then "Complete"
|
|
130
|
-
else "Unknown"
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
### Loops
|
|
134
|
-
|
|
135
|
-
```coffee
|
|
136
|
-
# Arrays
|
|
137
|
-
for item in items
|
|
138
|
-
console.log item
|
|
139
|
-
|
|
140
|
-
for item, i in items # with index
|
|
141
|
-
console.log "#{i}: #{item}"
|
|
142
|
-
|
|
143
|
-
# Objects
|
|
144
|
-
for key, value of object
|
|
145
|
-
console.log "#{key} = #{value}"
|
|
146
|
-
|
|
147
|
-
# Ranges
|
|
148
|
-
for i in [1..10] # inclusive (1 to 10)
|
|
149
|
-
for i in [1...10] # exclusive (1 to 9)
|
|
150
|
-
|
|
151
|
-
# Loop N times
|
|
152
|
-
loop 5
|
|
153
|
-
console.log "hi"
|
|
154
|
-
|
|
155
|
-
# Comprehensions
|
|
156
|
-
squares = (x * x for x in [1..10])
|
|
157
|
-
evens = (x for x in items when x % 2 is 0)
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
### Implicit `it`
|
|
161
|
-
|
|
162
|
-
Arrow functions with no params that reference `it` auto-inject it as the parameter:
|
|
163
|
-
|
|
164
|
-
```coffee
|
|
165
|
-
users.filter -> it.active # instead of (u) -> u.active
|
|
166
|
-
names = users.map -> it.name # instead of (u) -> u.name
|
|
167
|
-
orders.filter -> it.total > 100 # works with any expression
|
|
168
|
-
items.map => it.toUpperCase() # works with fat arrows too
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
---
|
|
172
|
-
|
|
173
|
-
## Operators
|
|
174
|
-
|
|
175
|
-
Rip extends JavaScript with powerful operators:
|
|
176
|
-
|
|
177
|
-
| Operator | Name | Example | Result |
|
|
178
|
-
|----------|------|---------|--------|
|
|
179
|
-
| `!` | Dammit | `fetchData!` | `await fetchData()` |
|
|
180
|
-
| `!` | Void | `def log!` | Function returns `undefined` |
|
|
181
|
-
| `=!` | Readonly | `MAX =! 100` | `const MAX = 100` |
|
|
182
|
-
| `!?` | Otherwise | `val !? 5` | Default if undefined/throws |
|
|
183
|
-
| `?` | Existence | `x?` | `x != null` |
|
|
184
|
-
| `?.` | Optional chain | `a?.b?.c` | ES6 optional chaining |
|
|
185
|
-
| `?[]` | Optional index | `arr?[0]` | `arr?.[0]` |
|
|
186
|
-
| `??` | Nullish | `a ?? b` | ES6 nullish coalescing |
|
|
187
|
-
| `//` | Floor div | `7 // 2` | `3` |
|
|
188
|
-
| `%%` | True modulo | `-1 %% 3` | `2` (always positive) |
|
|
189
|
-
| `=~` | Regex match | `str =~ /pat/` | Match, captures in `_` |
|
|
190
|
-
| `:=` | State | `count := 0` | Reactive signal |
|
|
191
|
-
| `~=` | Computed | `doubled ~= x * 2` | Reactive computed |
|
|
192
|
-
| `~>` | Effect | `~> console.log x` | Reactive side effect |
|
|
193
|
-
| `::` | Prototype | `String::trim` | `String.prototype.trim` |
|
|
194
|
-
| `[-n]` | Negative index | `arr[-1]` | `arr.at(-1)` |
|
|
195
|
-
| `*` | String repeat | `"-" * 40` | `"-".repeat(40)` |
|
|
196
|
-
| `<` `<=` | Chained | `1 < x < 10` | `(1 < x) && (x < 10)` |
|
|
197
|
-
| `.=` | Method assign | `x .= trim()` | `x = x.trim()` |
|
|
198
|
-
| `*` | Merge assign | `*obj = {a: 1}` | `Object.assign(obj, {a: 1})` |
|
|
199
|
-
| `not in` | Not in | `x not in arr` | Negated membership |
|
|
200
|
-
| `not of` | Not of | `k not of obj` | Negated key existence |
|
|
201
|
-
| `if...else` | Postfix ternary | `"a" if x else "b"` | `x ? "a" : "b"` |
|
|
202
|
-
| `**` | Power | `2 ** 10` | `1024` |
|
|
203
|
-
| `..` | Range | `[1..5]` | Inclusive range |
|
|
204
|
-
| `...` | Spread/rest | `[...a, ...b]` | ES6 spread |
|
|
205
|
-
|
|
206
|
-
### Dammit Operator (`!`)
|
|
207
|
-
|
|
208
|
-
The most distinctive Rip operator. Appended to a function call, it both calls AND awaits:
|
|
209
|
-
|
|
210
|
-
```coffee
|
|
211
|
-
# These are equivalent:
|
|
212
|
-
data = fetchUsers!
|
|
213
|
-
data = await fetchUsers()
|
|
214
|
-
|
|
215
|
-
# With arguments:
|
|
216
|
-
user = getUser!(id)
|
|
217
|
-
user = await getUser(id)
|
|
218
|
-
|
|
219
|
-
# Functions are auto-async when they contain !
|
|
220
|
-
def loadData(id)
|
|
221
|
-
user = getUser!(id)
|
|
222
|
-
posts = getPosts!(user.id)
|
|
223
|
-
{user, posts}
|
|
224
|
-
# Compiles to: async function loadData(id) { ... }
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
### Regex Match (`=~`)
|
|
228
|
-
|
|
229
|
-
Ruby-style pattern matching with captures stored in `_`:
|
|
230
|
-
|
|
231
|
-
```coffee
|
|
232
|
-
if text =~ /Hello, (\w+)/
|
|
233
|
-
console.log "Found: #{_[1]}"
|
|
234
|
-
|
|
235
|
-
# Direct extraction via regex indexing
|
|
236
|
-
domain = "user@example.com"[/@(.+)$/, 1] # "example.com"
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
### Pipe Operator (`|>`)
|
|
240
|
-
|
|
241
|
-
Pipes a value into a function as the first argument. Chains left-to-right:
|
|
242
|
-
|
|
243
|
-
```coffee
|
|
244
|
-
5 |> double # → double(5)
|
|
245
|
-
5 |> add(3) # → add(5, 3)
|
|
246
|
-
5 |> double |> add(1) # → add(double(5), 1)
|
|
247
|
-
data |> JSON.stringify(null, 2) # → JSON.stringify(data, null, 2)
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
If the right side is a bare reference, it's called with the piped value. If the right side is already a call, the piped value is inserted as the first argument. This is the Elixir-style pipe — cleaner than F# (multi-arg works) and simpler than Hack (no placeholder needed).
|
|
251
|
-
|
|
252
|
-
### Guard Clauses
|
|
253
|
-
|
|
254
|
-
```coffee
|
|
255
|
-
def loadUser(id)
|
|
256
|
-
data = fetchUser!(id) or return {error: "Not found"}
|
|
257
|
-
token = headers.auth or throw new Error "No auth"
|
|
258
|
-
port = config.port ?? return 3000
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
### Implicit Commas
|
|
262
|
-
|
|
263
|
-
When a literal value is followed by an arrow function, Rip inserts a comma automatically:
|
|
264
|
-
|
|
265
|
-
```coffee
|
|
266
|
-
# Clean route handlers
|
|
267
|
-
get '/users' -> User.all!
|
|
268
|
-
get '/users/:id' -> User.find params.id
|
|
269
|
-
post '/users' -> User.create body
|
|
270
|
-
```
|
|
271
|
-
|
|
272
|
-
---
|
|
273
|
-
|
|
274
|
-
## Reactivity
|
|
275
|
-
|
|
276
|
-
Rip provides fine-grained reactivity as language-level operators, not library imports:
|
|
277
|
-
|
|
278
|
-
```coffee
|
|
279
|
-
# State — reactive container
|
|
280
|
-
count := 0
|
|
281
|
-
|
|
282
|
-
# Computed — derived value (lazy, cached)
|
|
283
|
-
doubled ~= count * 2
|
|
284
|
-
message ~= "Count is #{count}"
|
|
285
|
-
|
|
286
|
-
# Effect — side effect, re-runs when dependencies change
|
|
287
|
-
~> console.log "Count changed to #{count}"
|
|
288
|
-
|
|
289
|
-
# Update state (triggers dependents)
|
|
290
|
-
count = 5
|
|
291
|
-
# doubled is now 10
|
|
292
|
-
# effect logs: "Count changed to 5"
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
### Reactive Methods
|
|
296
|
-
|
|
297
|
-
```coffee
|
|
298
|
-
count := 0
|
|
299
|
-
count.read() # Get value without tracking
|
|
300
|
-
count.lock() # Make readonly
|
|
301
|
-
count.free() # Unsubscribe from dependencies
|
|
302
|
-
count.kill() # Clean up, return final value
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
---
|
|
306
|
-
|
|
307
|
-
## Type Annotations
|
|
308
|
-
|
|
309
|
-
Rip's optional type system adds annotations that are **erased** from JavaScript output and **emitted** as `.d.ts` files for TypeScript interoperability.
|
|
310
|
-
|
|
311
|
-
```coffee
|
|
312
|
-
# Annotate parameters and return types
|
|
313
|
-
def greet(name:: string):: string
|
|
314
|
-
"Hello, #{name}!"
|
|
315
|
-
|
|
316
|
-
# Type aliases
|
|
317
|
-
User ::= type
|
|
318
|
-
id: number
|
|
319
|
-
name: string
|
|
320
|
-
email?: string # optional property
|
|
321
|
-
|
|
322
|
-
# Interfaces
|
|
323
|
-
interface Animal
|
|
324
|
-
name: string
|
|
325
|
-
speak: => void
|
|
326
|
-
|
|
327
|
-
# Enums (emit runtime JS + .d.ts)
|
|
328
|
-
enum Status
|
|
329
|
-
Active
|
|
330
|
-
Inactive
|
|
331
|
-
Pending
|
|
332
|
-
|
|
333
|
-
# Generate .d.ts
|
|
334
|
-
# rip -d myfile.rip
|
|
335
|
-
```
|
|
336
|
-
|
|
337
|
-
---
|
|
338
|
-
|
|
339
|
-
## Modules
|
|
340
|
-
|
|
341
|
-
Standard ES6 module syntax:
|
|
342
|
-
|
|
343
|
-
```coffee
|
|
344
|
-
# Import
|
|
345
|
-
import express from 'express'
|
|
346
|
-
import { readFile } from 'fs'
|
|
347
|
-
import * as path from 'path'
|
|
348
|
-
|
|
349
|
-
# Export
|
|
350
|
-
export def processData(data)
|
|
351
|
-
data.map (x) -> x * 2
|
|
352
|
-
|
|
353
|
-
export default { process: processData }
|
|
354
|
-
|
|
355
|
-
# Re-export
|
|
356
|
-
export { foo, bar } from './utils'
|
|
357
|
-
```
|
|
358
|
-
|
|
359
|
-
---
|
|
360
|
-
|
|
361
|
-
## Async Patterns
|
|
362
|
-
|
|
363
|
-
```coffee
|
|
364
|
-
# Traditional await
|
|
365
|
-
user = await getUser(id)
|
|
366
|
-
|
|
367
|
-
# Dammit operator (call + await)
|
|
368
|
-
user = getUser!(id)
|
|
369
|
-
posts = getPosts!(user.id)
|
|
370
|
-
|
|
371
|
-
# Error handling
|
|
372
|
-
try
|
|
373
|
-
data = fetchData!
|
|
374
|
-
catch error
|
|
375
|
-
console.error error
|
|
376
|
-
|
|
377
|
-
# Async iteration
|
|
378
|
-
for item as! asyncIterable
|
|
379
|
-
console.log item
|
|
380
|
-
```
|
|
381
|
-
|
|
382
|
-
---
|
|
383
|
-
|
|
384
|
-
## Packages
|
|
385
|
-
|
|
386
|
-
Rip includes optional packages for full-stack development. All are written in Rip, have zero dependencies, and run on Bun.
|
|
387
|
-
|
|
388
|
-
```bash
|
|
389
|
-
bun add @rip-lang/api # Web framework
|
|
390
|
-
bun add @rip-lang/server # Production server
|
|
391
|
-
bun add @rip-lang/db # DuckDB server
|
|
392
|
-
bun add @rip-lang/ui # Reactive web UI
|
|
393
|
-
bun add @rip-lang/schema # ORM + validation
|
|
394
|
-
bun add @rip-lang/swarm # Parallel job runner
|
|
395
|
-
bun add @rip-lang/csv # CSV parser + writer
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
### @rip-lang/api — Web Framework
|
|
399
|
-
|
|
400
|
-
Sinatra-style routing with `@` context magic and 37 built-in validators.
|
|
401
|
-
|
|
402
|
-
```coffee
|
|
403
|
-
import { get, post, use, read, start, notFound } from '@rip-lang/api'
|
|
404
|
-
|
|
405
|
-
# Routes — return data directly
|
|
406
|
-
get '/' -> { message: 'Hello!' }
|
|
407
|
-
get '/users/:id' -> User.find!(read 'id', 'id!')
|
|
408
|
-
|
|
409
|
-
# Form validation with read()
|
|
410
|
-
post '/signup' ->
|
|
411
|
-
email = read 'email', 'email!' # required email
|
|
412
|
-
age = read 'age', 'int', [18, 120] # integer between 18-120
|
|
413
|
-
role = read 'role', ['admin', 'user'] # enum
|
|
414
|
-
{ success: true, email, age, role }
|
|
415
|
-
|
|
416
|
-
# File serving
|
|
417
|
-
get '/css/*' -> @send "public/#{@req.path.slice(5)}"
|
|
418
|
-
notFound -> @send 'index.html', 'text/html; charset=UTF-8'
|
|
419
|
-
|
|
420
|
-
# Middleware
|
|
421
|
-
import { cors, logger, sessions } from '@rip-lang/api/middleware'
|
|
422
|
-
|
|
423
|
-
use logger()
|
|
424
|
-
use cors origin: '*'
|
|
425
|
-
use sessions secret: process.env.SECRET
|
|
426
|
-
|
|
427
|
-
# Lifecycle hooks
|
|
428
|
-
before -> @start = Date.now()
|
|
429
|
-
after -> console.log "#{@req.method} #{@req.path} - #{Date.now() - @start}ms"
|
|
430
|
-
|
|
431
|
-
start port: 3000
|
|
432
|
-
```
|
|
433
|
-
|
|
434
|
-
#### read() Validators
|
|
435
|
-
|
|
436
|
-
```coffee
|
|
437
|
-
id = read 'id', 'id!' # positive integer (required)
|
|
438
|
-
count = read 'count', 'whole' # non-negative integer
|
|
439
|
-
price = read 'price', 'money' # cents (multiplies by 100)
|
|
440
|
-
name = read 'name', 'string' # collapses whitespace
|
|
441
|
-
email = read 'email', 'email' # valid email format
|
|
442
|
-
phone = read 'phone', 'phone' # US phone → (555) 123-4567
|
|
443
|
-
state = read 'state', 'state' # two-letter → uppercase
|
|
444
|
-
zip = read 'zip', 'zip' # 5-digit zip
|
|
445
|
-
url = read 'url', 'url' # valid URL
|
|
446
|
-
uuid = read 'id', 'uuid' # UUID format
|
|
447
|
-
date = read 'date', 'date' # YYYY-MM-DD
|
|
448
|
-
time = read 'time', 'time' # HH:MM or HH:MM:SS
|
|
449
|
-
flag = read 'flag', 'bool' # boolean
|
|
450
|
-
tags = read 'tags', 'array' # must be array
|
|
451
|
-
ids = read 'ids', 'ids' # "1,2,3" → [1, 2, 3]
|
|
452
|
-
slug = read 'slug', 'slug' # URL-safe slug
|
|
453
|
-
```
|
|
454
|
-
|
|
455
|
-
### @rip-lang/server — Production Server
|
|
456
|
-
|
|
457
|
-
Multi-worker process manager with hot reload, HTTPS, and mDNS.
|
|
458
|
-
|
|
459
|
-
```bash
|
|
460
|
-
rip-server # Start (uses ./index.rip)
|
|
461
|
-
rip-server -w # With file watching + hot-reload
|
|
462
|
-
rip-server myapp # Named (accessible at myapp.local)
|
|
463
|
-
rip-server http:3000 # HTTP on specific port
|
|
464
|
-
```
|
|
465
|
-
|
|
466
|
-
### @rip-lang/db — DuckDB Server
|
|
467
|
-
|
|
468
|
-
HTTP server for DuckDB with JSONCompact responses.
|
|
469
|
-
|
|
470
|
-
```bash
|
|
471
|
-
rip-db mydata.duckdb --port=4000
|
|
472
|
-
```
|
|
473
|
-
|
|
474
|
-
```coffee
|
|
475
|
-
# Query from Rip
|
|
476
|
-
import { get, start } from '@rip-lang/api'
|
|
477
|
-
import { DB } from '@rip-lang/db'
|
|
478
|
-
|
|
479
|
-
db = DB.new 'data.duckdb'
|
|
480
|
-
|
|
481
|
-
get '/users' -> db.query! "SELECT * FROM users"
|
|
482
|
-
get '/users/:id' -> db.query! "SELECT * FROM users WHERE id = ?", [read 'id', 'id!']
|
|
483
|
-
|
|
484
|
-
start port: 3000
|
|
485
|
-
```
|
|
486
|
-
|
|
487
|
-
### @rip-lang/ui — Reactive Web Framework
|
|
488
|
-
|
|
489
|
-
Zero-build framework. The browser loads the Rip compiler, compiles the UI
|
|
490
|
-
framework, fetches an app bundle, and renders. No build step, no bundler.
|
|
491
|
-
|
|
492
|
-
```coffee
|
|
493
|
-
# Server (index.rip)
|
|
494
|
-
import { get, use, start, notFound } from '@rip-lang/api'
|
|
495
|
-
import { ripUI } from '@rip-lang/ui/serve'
|
|
496
|
-
|
|
497
|
-
dir = import.meta.dir
|
|
498
|
-
use ripUI dir: dir, watch: true, title: 'My App'
|
|
499
|
-
get '/css/*', -> @send "#{dir}/css/#{@req.path.slice(5)}"
|
|
500
|
-
notFound -> @send "#{dir}/index.html", 'text/html; charset=UTF-8'
|
|
501
|
-
start port: 3000
|
|
502
|
-
```
|
|
503
|
-
|
|
504
|
-
```html
|
|
505
|
-
<!-- index.html -->
|
|
506
|
-
<script type="module" src="/rip/browser.js"></script>
|
|
507
|
-
<script type="text/rip">
|
|
508
|
-
{ launch } = importRip! '/rip/ui.rip'
|
|
509
|
-
launch()
|
|
510
|
-
</script>
|
|
511
|
-
```
|
|
512
|
-
|
|
513
|
-
```coffee
|
|
514
|
-
# Component (parts/counter.rip)
|
|
515
|
-
export Counter = component
|
|
516
|
-
@count := 0
|
|
517
|
-
doubled ~= @count * 2
|
|
518
|
-
|
|
519
|
-
increment: -> @count += 1
|
|
520
|
-
|
|
521
|
-
render
|
|
522
|
-
div.counter
|
|
523
|
-
h1 "Count: #{@count}"
|
|
524
|
-
p "Doubled: #{doubled}"
|
|
525
|
-
button @click: @increment, "+"
|
|
526
|
-
```
|
|
527
|
-
|
|
528
|
-
### @rip-lang/swarm — Parallel Job Runner
|
|
529
|
-
|
|
530
|
-
```coffee
|
|
531
|
-
import { swarm, init, retry, todo } from '@rip-lang/swarm'
|
|
532
|
-
|
|
533
|
-
setup = ->
|
|
534
|
-
unless retry()
|
|
535
|
-
init()
|
|
536
|
-
for i in [1..100] then todo(i)
|
|
537
|
-
|
|
538
|
-
perform = (task, ctx) ->
|
|
539
|
-
await Bun.sleep(Math.random() * 1000)
|
|
540
|
-
|
|
541
|
-
swarm { setup, perform }
|
|
542
|
-
```
|
|
543
|
-
|
|
544
|
-
### @rip-lang/csv — CSV Parser + Writer
|
|
545
|
-
|
|
546
|
-
```coffee
|
|
547
|
-
import { CSV } from '@rip-lang/csv'
|
|
548
|
-
|
|
549
|
-
# Parse
|
|
550
|
-
rows = CSV.read "name,age\nAlice,30\nBob,25\n", headers: true
|
|
551
|
-
# [{name: 'Alice', age: '30'}, {name: 'Bob', age: '25'}]
|
|
552
|
-
|
|
553
|
-
# Write
|
|
554
|
-
CSV.save! 'output.csv', rows
|
|
555
|
-
```
|
|
556
|
-
|
|
557
|
-
### @rip-lang/schema — ORM + Validation
|
|
558
|
-
|
|
559
|
-
```coffee
|
|
560
|
-
import { Model } from '@rip-lang/schema'
|
|
561
|
-
|
|
562
|
-
class User extends Model
|
|
563
|
-
@table = 'users'
|
|
564
|
-
@schema
|
|
565
|
-
name: { type: 'string', required: true }
|
|
566
|
-
email: { type: 'email', unique: true }
|
|
567
|
-
|
|
568
|
-
user = User.find!(25)
|
|
569
|
-
user.name = 'Alice'
|
|
570
|
-
user.save!()
|
|
571
|
-
```
|
|
572
|
-
|
|
573
|
-
---
|
|
574
|
-
|
|
575
|
-
## Browser
|
|
576
|
-
|
|
577
|
-
Rip runs directly in the browser with full async/await support — no build step.
|
|
578
|
-
|
|
579
|
-
### Inline Scripts
|
|
580
|
-
|
|
581
|
-
Load the compiler, then write Rip:
|
|
582
|
-
|
|
583
|
-
```html
|
|
584
|
-
<script type="module" src="/rip/browser.js"></script>
|
|
585
|
-
<script type="text/rip">
|
|
586
|
-
res = fetch! 'https://api.example.com/data'
|
|
587
|
-
data = res.json!
|
|
588
|
-
console.log data
|
|
589
|
-
</script>
|
|
590
|
-
```
|
|
591
|
-
|
|
592
|
-
The `!` operator works in inline scripts — `processRipScripts` wraps
|
|
593
|
-
compiled code in an async IIFE transparently.
|
|
594
|
-
|
|
595
|
-
### Console REPL
|
|
596
|
-
|
|
597
|
-
The `rip()` function is available in any browser console where `rip.browser.js`
|
|
598
|
-
is loaded:
|
|
599
|
-
|
|
600
|
-
```javascript
|
|
601
|
-
rip("42 * 10 + 8") // → 428
|
|
602
|
-
rip("name = 'Alice'") // → 'Alice' (persists on globalThis)
|
|
603
|
-
rip("(x * x for x in [1..5])") // → [1, 4, 9, 16, 25]
|
|
604
|
-
|
|
605
|
-
// Async — use await, Chrome displays the resolved value
|
|
606
|
-
await rip("res = fetch! 'https://jsonplaceholder.typicode.com/todos/1'; res.json!")
|
|
607
|
-
// → {userId: 1, id: 1, title: 'delectus aut autem', completed: false}
|
|
608
|
-
```
|
|
609
|
-
|
|
610
|
-
Sync code returns values directly. Async code (using `!`) returns a Promise
|
|
611
|
-
that Chrome auto-awaits. Variables persist between calls on `globalThis`.
|
|
612
|
-
|
|
613
|
-
### `importRip(url)`
|
|
614
|
-
|
|
615
|
-
Fetch, compile, and import a `.rip` file as an ES module:
|
|
616
|
-
|
|
617
|
-
```html
|
|
618
|
-
<script type="text/rip">
|
|
619
|
-
{ launch } = importRip! '/rip/ui.rip'
|
|
620
|
-
launch '/demo'
|
|
621
|
-
</script>
|
|
622
|
-
```
|
|
623
|
-
|
|
624
|
-
---
|
|
625
|
-
|
|
626
|
-
## Full-Stack Example
|
|
627
|
-
|
|
628
|
-
A complete API server in Rip:
|
|
629
|
-
|
|
630
|
-
```coffee
|
|
631
|
-
import { get, post, use, read, start, notFound } from '@rip-lang/api'
|
|
632
|
-
import { cors, logger } from '@rip-lang/api/middleware'
|
|
633
|
-
|
|
634
|
-
use logger()
|
|
635
|
-
use cors origin: '*'
|
|
636
|
-
|
|
637
|
-
# In-memory store
|
|
638
|
-
users = []
|
|
639
|
-
nextId = 1
|
|
640
|
-
|
|
641
|
-
get '/api/users' -> users
|
|
642
|
-
|
|
643
|
-
get '/api/users/:id' ->
|
|
644
|
-
id = read 'id', 'id!'
|
|
645
|
-
user = users.find (u) -> u.id is id
|
|
646
|
-
user or throw { status: 404, message: 'Not found' }
|
|
647
|
-
|
|
648
|
-
post '/api/users' ->
|
|
649
|
-
name = read 'name', 'string!'
|
|
650
|
-
email = read 'email', 'email!'
|
|
651
|
-
user = { id: nextId++, name, email }
|
|
652
|
-
users.push user
|
|
653
|
-
user
|
|
654
|
-
|
|
655
|
-
notFound -> { error: 'Not found' }
|
|
656
|
-
|
|
657
|
-
start port: 3000
|
|
658
|
-
```
|
|
659
|
-
|
|
660
|
-
---
|
|
661
|
-
|
|
662
|
-
## CLI Reference
|
|
663
|
-
|
|
664
|
-
```bash
|
|
665
|
-
rip # REPL
|
|
666
|
-
rip file.rip # Run
|
|
667
|
-
rip -c file.rip # Compile to JS (stdout)
|
|
668
|
-
rip -o out.js file.rip # Compile to file
|
|
669
|
-
rip -m file.rip # Compile with inline source map
|
|
670
|
-
rip -d file.rip # Generate .d.ts
|
|
671
|
-
rip -t file.rip # Show tokens
|
|
672
|
-
rip -s file.rip # Show S-expressions
|
|
673
|
-
rip -q -c file.rip # Quiet (no headers)
|
|
674
|
-
bun run test # Run test suite
|
|
675
|
-
```
|
|
676
|
-
|
|
677
|
-
---
|
|
678
|
-
|
|
679
|
-
## Documentation
|
|
680
|
-
|
|
681
|
-
| Document | Audience | Purpose |
|
|
682
|
-
|----------|----------|---------|
|
|
683
|
-
| **[RIP-GUIDE.md](RIP-GUIDE.md)** | Users / AI | Practical guide for using Rip in projects |
|
|
684
|
-
| **[AGENT.md](../AGENT.md)** | AI agents | Get up to speed for working on the compiler |
|
|
685
|
-
| **[RIP-LANG.md](RIP-LANG.md)** | Users | Full language reference |
|
|
686
|
-
| **[RIP-TYPES.md](RIP-TYPES.md)** | Contributors | Type system specification |
|
|
687
|
-
| **[RIP-REACTIVITY.md](RIP-REACTIVITY.md)** | Users | Reactivity deep dive |
|
|
688
|
-
| **[RIP-INTERNALS.md](RIP-INTERNALS.md)** | Contributors | Compiler architecture |
|
|
689
|
-
|
|
690
|
-
## Resources
|
|
691
|
-
|
|
692
|
-
- [Rip Playground](https://shreeve.github.io/rip-lang/) — Try Rip in the browser
|
|
693
|
-
- [VS Code Extension](https://marketplace.visualstudio.com/items?itemName=rip-lang.rip) — IDE support
|
|
694
|
-
- [GitHub](https://github.com/shreeve/rip-lang) — Source code
|
|
695
|
-
|
|
696
|
-
---
|
|
697
|
-
|
|
698
|
-
*Rip — Start simple. Build incrementally. Ship elegantly.*
|