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/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.*