rip-lang 3.6.2 → 3.7.3

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-LANG.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Rip Language Reference
4
4
 
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.
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,800 LOC.
6
6
 
7
7
  ---
8
8
 
@@ -121,6 +121,10 @@ else
121
121
  status = active ? "on" : "off"
122
122
  result = x > 5 ? "big" : "small"
123
123
 
124
+ # Ternary (Python-style postfix)
125
+ status = "active" if online else "offline"
126
+ label = "big" if x > 5 else "small"
127
+
124
128
  # NOTE: Subscript in ternary true-branch needs parentheses
125
129
  item = found ? (arr[0]) : default
126
130
 
@@ -227,6 +231,15 @@ until done
227
231
  loop
228
232
  data = fetch()
229
233
  break if data.complete
234
+
235
+ # Loop N times
236
+ loop 5
237
+ console.log "hi"
238
+ # Compiles to: for (let _i = 0; _i < 5; _i++) { ... }
239
+
240
+ # Loop with variable or expression
241
+ loop retries
242
+ attempt!
230
243
  ```
231
244
 
232
245
  ## Comprehensions
@@ -275,6 +288,7 @@ Multiple lines
275
288
  | `of` | `k of obj` | Object key existence |
276
289
  | `?` (postfix) | `a?` | Existence check (`a != null`) |
277
290
  | `?` (ternary) | `a ? b : c` | Ternary conditional |
291
+ | `if...else` (postfix) | `b if a else c` | Python-style ternary |
278
292
  | `?.` `?.[]` `?.()` | `a?.b` `a?.[0]` `a?.()` | Optional chaining (ES6) |
279
293
  | `?[]` `?()` | `a?[0]` `a?(x)` | Optional chaining shorthand |
280
294
  | `??` | `a ?? b` | Nullish coalescing |
@@ -293,6 +307,15 @@ Multiple lines
293
307
  | `!` | Void | `def process!` | Suppresses implicit return |
294
308
  | `!?` | Otherwise | `val !? 5` | Default if undefined or throws |
295
309
  | `=~` | Match | `str =~ /pat/` | Ruby-style regex match, captures in `_` |
310
+ | `::` | Prototype | `String::trim` | `String.prototype.trim` |
311
+ | `[-n]` | Negative index | `arr[-1]` | `arr.at(-1)` |
312
+ | `*` | String repeat | `"-" * 40` | `"-".repeat(40)` |
313
+ | `<` `<=` | Chained comparison | `1 < x < 10` | `(1 < x) && (x < 10)` |
314
+ | `\|>` | Pipe | `x \|> fn` or `x \|> fn(y)` | `fn(x)` or `fn(x, y)` |
315
+ | `.=` | Method assign | `x .= trim()` | `x = x.trim()` |
316
+ | `*` | Merge assign | `*obj = {a: 1}` | `Object.assign(obj, {a: 1})` |
317
+ | `not in` | Not in | `x not in arr` | Negated membership test |
318
+ | `not of` | Not of | `k not of obj` | Negated key existence |
296
319
 
297
320
  ## Assignment Operators
298
321
 
@@ -333,6 +356,10 @@ status = active ? 'on' : 'off'
333
356
  result = valid ? obj.field : null
334
357
  output = ready ? compute() : fallback
335
358
 
359
+ # Python-style postfix ternary
360
+ status = "active" if online else "offline"
361
+ label = "big" if x > 5 else "small"
362
+
336
363
  # Nested
337
364
  level = score > 90 ? 'A' : score > 80 ? 'B' : score > 70 ? 'C' : 'F'
338
365
 
@@ -349,6 +376,162 @@ result = riskyOperation() !? "default"
349
376
  # If riskyOperation() throws or returns null/undefined, result = "default"
350
377
  ```
351
378
 
379
+ ## Method Assignment (`.=`)
380
+
381
+ A Rip original. Compound assignment for method calls — apply a method to a
382
+ variable and assign the result back in one step:
383
+
384
+ ```coffee
385
+ # Without .= — repeat the variable name every time
386
+ items = items.filter -> it.active
387
+ items = items.map -> it.name
388
+ items = items.sort (a, b) -> a.localeCompare b
389
+ str = str.trim()
390
+ str = str.toLowerCase()
391
+
392
+ # With .= — name it once, transform in place
393
+ items .= filter -> it.active
394
+ items .= map -> it.name
395
+ items .= sort (a, b) -> a.localeCompare b
396
+ str .= trim()
397
+ str .= toLowerCase()
398
+ ```
399
+
400
+ `x .= method(args)` compiles to `x = x.method(args)`. It's the method-call
401
+ equivalent of `+=` — just as `x += 5` means `x = x + 5`, `x .= trim()`
402
+ means `x = x.trim()`.
403
+
404
+ This operator is unique to Rip. Other languages have `+=`, `-=`, `*=`, and
405
+ other arithmetic compound assignments, but none extend the concept to method
406
+ calls. Combined with implicit `it`, this enables remarkably concise data
407
+ transformation pipelines:
408
+
409
+ ```coffee
410
+ users .= filter -> it.active
411
+ users .= map -> it.name
412
+ users .= sort()
413
+ ```
414
+
415
+ Works with any method — built-in or custom, with or without arguments. Spacing
416
+ is flexible — all of these are equivalent:
417
+
418
+ ```coffee
419
+ str .= trim() # canonical (spaced)
420
+ str.=trim() # compact (no spaces)
421
+ str .=trim() # mixed
422
+ ```
423
+
424
+ ## Merge Assignment (`*`)
425
+
426
+ A Rip original. Merge properties into an existing object without repeating
427
+ its name:
428
+
429
+ ```coffee
430
+ # Without * — repeat the object name or use verbose Object.assign
431
+ Object.assign config,
432
+ host: "localhost"
433
+ port: 3000
434
+ debug: true
435
+
436
+ # With * — clean and direct
437
+ *config =
438
+ host: "localhost"
439
+ port: 3000
440
+ debug: true
441
+
442
+ # Single line
443
+ *opts = {method: "POST", body: data}
444
+
445
+ # Dotted paths
446
+ *el.style =
447
+ color: "red"
448
+ fontSize: "14px"
449
+
450
+ # Merge user overrides into defaults
451
+ *defaults = userConfig
452
+ ```
453
+
454
+ `*target = value` compiles to `Object.assign(target, value)`. The `*` reads
455
+ as "spread these into" — the same concept as `...` spread but as an
456
+ assignment. This is unique to Rip.
457
+
458
+ Common use cases: config objects, options bags, state initialization, DOM
459
+ styling, merging defaults with overrides — anywhere you're setting multiple
460
+ properties on an existing object.
461
+
462
+ ## Prototype Operator (`::`)
463
+
464
+ Access `.prototype` with `::` (CoffeeScript-style). Disambiguated from type annotations by spacing:
465
+
466
+ ```coffee
467
+ # Prototype access (no space after ::)
468
+ String::starts = String::startsWith
469
+ String::ends = String::endsWith
470
+ String::has = String::includes
471
+
472
+ # Now you can use them
473
+ "hello".starts "he" # true
474
+ "hello.rip".ends ".rip" # true
475
+ "error: bad".has "error" # true
476
+
477
+ # Define new prototype methods
478
+ String::shout = -> @toUpperCase() + "!"
479
+ "hello".shout() # "HELLO!"
480
+
481
+ # Type annotations (space after ::) — unaffected
482
+ name:: string = "Alice"
483
+ def greet(name:: string):: string
484
+ "Hello, #{name}!"
485
+ ```
486
+
487
+ The rule is simple: `::` with **no space** before an identifier is prototype access. `::` with a **space** is a type annotation.
488
+
489
+ ## Negative Indexing
490
+
491
+ Literal negative indexes compile to `.at()` for Python-style access from the end:
492
+
493
+ ```coffee
494
+ arr = [10, 20, 30, 40, 50]
495
+
496
+ arr[-1] # → arr.at(-1) — 50 (last)
497
+ arr[-2] # → arr.at(-2) — 40 (second to last)
498
+ str[-1] # works on strings too
499
+
500
+ arr?[-1] # → arr?.at(-1) — optional variant
501
+
502
+ # Positive and variable indexes are unchanged
503
+ arr[0] # → arr[0] — normal access
504
+ arr[i] # → arr[i] — variable index
505
+ ```
506
+
507
+ Only literal negative numbers trigger the `.at()` transform. Variable indexes pass through as-is.
508
+
509
+ ## Pipe Operator (`|>`)
510
+
511
+ Pipes a value into a function as its first argument. Chains left-to-right:
512
+
513
+ ```coffee
514
+ # Simple reference — value becomes the sole argument
515
+ 5 |> double # → double(5)
516
+ 10 |> Math.sqrt # → Math.sqrt(10)
517
+ "hello" |> console.log # → console.log("hello")
518
+
519
+ # Multi-arg — value is inserted as the FIRST argument
520
+ 5 |> add(3) # → add(5, 3)
521
+ data |> filter(isActive) # → filter(data, isActive)
522
+ "World" |> greet("!") # → greet("World", "!")
523
+
524
+ # Chaining — reads left-to-right like a pipeline
525
+ 5 |> double |> add(1) |> console.log
526
+ # → console.log(add(double(5), 1))
527
+
528
+ # Works with dotted methods
529
+ users |> Array.from # → Array.from(users)
530
+ data |> JSON.stringify(null, 2) # → JSON.stringify(data, null, 2)
531
+ ```
532
+
533
+ This is the **Elixir-style** pipe — strictly better than F#'s (which only supports bare function references) and cleaner than Hack's (which requires a `%` placeholder). No special syntax to learn; if the right side is a call, the left value goes first.
534
+
352
535
  ---
353
536
 
354
537
  # 4. Functions
@@ -420,6 +603,41 @@ def findUser(id)
420
603
  null
421
604
  ```
422
605
 
606
+ ## Implicit `it` Parameter
607
+
608
+ Arrow functions with no explicit parameters that reference `it` in the body automatically inject `it` as the parameter:
609
+
610
+ ```coffee
611
+ # Without `it` — must name a throwaway variable
612
+ users.filter (u) -> u.active
613
+ names = users.map (u) -> u.name
614
+
615
+ # With `it` — cleaner
616
+ users.filter -> it.active
617
+ names = users.map -> it.name
618
+ orders.filter -> it.total > 100
619
+
620
+ # Works with fat arrows too
621
+ items.map => it.toUpperCase()
622
+
623
+ # Nested arrows — each level gets its own `it`
624
+ # Only the innermost param-less arrow with `it` is affected
625
+ groups.map -> it.items.filter -> it.active
626
+
627
+ # Explicit params still work normally
628
+ items.sort (a, b) -> a - b
629
+ ```
630
+
631
+ Compiles to standard JavaScript — `it` becomes a regular function parameter:
632
+
633
+ ```coffee
634
+ arr.filter -> it > 5
635
+ # → arr.filter(function(it) { return (it > 5); })
636
+
637
+ arr.map => it.name
638
+ # → arr.map(it => it.name)
639
+ ```
640
+
423
641
  ## Calling Functions
424
642
 
425
643
  ```coffee
@@ -1165,4 +1383,4 @@ count = 10 # Logs: "Count: 10, Doubled: 20"
1165
1383
 
1166
1384
  ---
1167
1385
 
1168
- *Rip 3.4 — 1,140 tests passing — Zero dependencies — Self-hosting — ~10,300 LOC*
1386
+ *Rip 3.7 — 1,219 tests passing — Zero dependencies — Self-hosting — ~10,800 LOC*