rip-lang 3.13.2 → 3.13.4

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/README.md CHANGED
@@ -9,9 +9,9 @@
9
9
  </p>
10
10
 
11
11
  <p align="center">
12
- <a href="CHANGELOG.md"><img src="https://img.shields.io/badge/version-3.13.2-blue.svg" alt="Version"></a>
12
+ <a href="CHANGELOG.md"><img src="https://img.shields.io/badge/version-3.13.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-1%2C243%2F1%2C243-brightgreen.svg" alt="Tests"></a>
14
+ <a href="#"><img src="https://img.shields.io/badge/tests-1%2C251%2F1%2C251-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
 
@@ -172,6 +172,29 @@ enum HttpCode # Runtime enum
172
172
 
173
173
  Compiles to `.js` (types erased) + `.d.ts` (types preserved) — full IDE support via TypeScript Language Server. See [docs/RIP-TYPES.md](docs/RIP-TYPES.md).
174
174
 
175
+ ### Standard Library
176
+
177
+ 13 global helpers available in every Rip program — no imports needed:
178
+
179
+ ```coffee
180
+ p "hello" # console.log shorthand
181
+ pp {name: "Alice", age: 30} # pretty-print JSON (also returns value)
182
+ warn "deprecated" # console.warn
183
+ assert x > 0, "must be positive"
184
+ raise TypeError, "expected string"
185
+ todo "finish this later"
186
+ kind [1, 2, 3] # "array" (fixes typeof)
187
+ rand 10 # 0-9
188
+ rand 5, 10 # 5-10 inclusive
189
+ sleep! 1000 # await sleep(1000)
190
+ exit 1 # process.exit(1)
191
+ abort "fatal" # log to stderr + exit(1)
192
+ zip names, ages # [[n1,a1], [n2,a2], ...]
193
+ noop # () => {}
194
+ ```
195
+
196
+ All use `globalThis` with `??=` — override any by redeclaring locally.
197
+
175
198
  ---
176
199
 
177
200
  ## Operators
@@ -180,7 +203,8 @@ Compiles to `.js` (types erased) + `.d.ts` (types preserved) — full IDE suppor
180
203
  |----------|---------|--------------|
181
204
  | `!` (dammit) | `fetchData!` | Calls AND awaits |
182
205
  | `!` (void) | `def process!` | Suppresses implicit return |
183
- | `!?` (otherwise) | `val !? 5` | Default only if `undefined` |
206
+ | `!?` (otherwise) | `val !? 5` | Default only if `undefined` (infix) |
207
+ | `!?` (defined) | `val!?` | True if not `undefined` (postfix) |
184
208
  | `?` (existence) | `x?` | True if `x != null` |
185
209
  | `?:` (ternary) | `x > 0 ? 'yes' : 'no'` | JS-style ternary expression |
186
210
  | `if...else` (postfix) | `"yes" if cond else "no"` | Python-style ternary expression |
@@ -403,7 +427,7 @@ rip file.rip # Run
403
427
  rip -c file.rip # Compile
404
428
  rip -t file.rip # Tokens
405
429
  rip -s file.rip # S-expressions
406
- bun run test # 1243 tests
430
+ bun run test # 1251 tests
407
431
  bun run parser # Rebuild parser
408
432
  bun run browser # Build browser bundle
409
433
  ```
@@ -417,7 +441,7 @@ bun run browser # Build browser bundle
417
441
  | [docs/RIP-LANG.md](docs/RIP-LANG.md) | Full language reference (syntax, operators, reactivity, types, future ideas) |
418
442
  | [docs/RIP-INTERNALS.md](docs/RIP-INTERNALS.md) | Compiler architecture (lexer, parser, codegen, S-expressions) |
419
443
  | [docs/RIP-TYPES.md](docs/RIP-TYPES.md) | Type system specification |
420
- | [AGENT.md](AGENT.md) | AI agents — get up to speed for working on the compiler |
444
+ | [AGENTS.md](AGENTS.md) | AI agents — get up to speed for working on the compiler |
421
445
 
422
446
  ---
423
447
 
package/bin/rip CHANGED
@@ -12,6 +12,12 @@ import packageJson from '../package.json' with { type: 'json' };
12
12
  const __filename = fileURLToPath(import.meta.url);
13
13
  const __dirname = dirname(__filename);
14
14
 
15
+ // Set NODE_PATH so spawned processes and workers can resolve @rip-lang/* packages
16
+ const _nodeModules = join(__dirname, '..', '..');
17
+ if (!process.env.NODE_PATH?.split(':').includes(_nodeModules)) {
18
+ process.env.NODE_PATH = [_nodeModules, process.env.NODE_PATH].filter(Boolean).join(':');
19
+ }
20
+
15
21
  const VERSION = packageJson.version;
16
22
  const SUMMARY = packageJson.description;
17
23
 
@@ -562,6 +562,19 @@ rip> .sexp # Toggle s-expression display
562
562
  rip> .js # Toggle JS display
563
563
  ```
564
564
 
565
+ ## REPL Architecture
566
+
567
+ The CLI REPL (`src/repl.js`) uses a `vm`-based sandbox with two eval paths:
568
+
569
+ - **Primary:** `vm.runInContext` with async IIFE wrapper — handles all code including `await`
570
+ - **Fallback:** `vm.SourceTextModule` — used only when `import` statements are present (required for module linking)
571
+
572
+ This split works around Bun bug [#22287](https://github.com/oven-sh/bun/issues/22287) where `vm.SourceTextModule` crashes on top-level `await`.
573
+
574
+ The stdlib is injected once at REPL startup via `getStdlibCode()` — the same function the compiler uses, ensuring a single source of truth. Variables persist across evaluations via a `__vars` object.
575
+
576
+ The browser REPL (`docs/index.html`) uses a hidden iframe as its sandbox. Code is compiled with `skipPreamble: true` and wrapped in an async IIFE for `await` support. The stdlib and reactive runtime are injected into the iframe context at initialization.
577
+
565
578
  ---
566
579
 
567
580
  # 9. Future Work
package/docs/RIP-LANG.md CHANGED
@@ -21,9 +21,10 @@ Rip is a modern reactive language that compiles to ES2022 JavaScript. It combine
21
21
  11. [CLI Tools & Scripts](#11-cli-tools--scripts)
22
22
  12. [Types](#12-types)
23
23
  13. [JavaScript Interop](#13-javascript-interop)
24
- 14. [Common Patterns](#14-common-patterns)
25
- 15. [Quick Reference](#15-quick-reference)
26
- 16. [Future Ideas](#16-future-ideas)
24
+ 14. [Standard Library](#14-standard-library)
25
+ 15. [Common Patterns](#15-common-patterns)
26
+ 16. [Quick Reference](#16-quick-reference)
27
+ 17. [Future Ideas](#17-future-ideas)
27
28
 
28
29
  ---
29
30
 
@@ -293,6 +294,7 @@ Multiple lines
293
294
  | `in` | `x in arr` | Array membership |
294
295
  | `of` | `k of obj` | Object key existence |
295
296
  | `?` (postfix) | `a?` | Existence check (`a != null`) |
297
+ | `!?` (postfix) | `a!?` | Defined check (`a !== undefined`) |
296
298
  | `?` (ternary) | `a ? b : c` | Ternary conditional |
297
299
  | `if...else` (postfix) | `b if a else c` | Python-style ternary |
298
300
  | `?.` `?.[]` `?.()` | `a?.b` `a?.[0]` `a?.()` | Optional chaining (ES6) |
@@ -311,7 +313,8 @@ Multiple lines
311
313
  | `%%` | True modulo | `-1 %% 3` | Always positive result (not remainder) |
312
314
  | `!` | Dammit | `fetchData!` | `await fetchData()` — calls AND awaits |
313
315
  | `!` | Void | `def process!` | Suppresses implicit return |
314
- | `!?` | Otherwise | `val !? 5` | Default if undefined or throws |
316
+ | `!?` | Otherwise | `val !? 5` | Default if undefined (infix) |
317
+ | `!?` | Defined | `val!?` | True if not undefined (postfix) |
315
318
  | `=~` | Match | `str =~ /pat/` | Ruby-style regex match, captures in `_` |
316
319
  | `::` | Prototype | `String::trim` | `String.prototype.trim` |
317
320
  | `[-n]` | Negative index | `arr[-1]` | `arr.at(-1)` |
@@ -374,15 +377,34 @@ level = score > 90 ? 'A' : score > 80 ? 'B' : score > 70 ? 'C' : 'F'
374
377
  item = found ? (arr[0]) : default
375
378
  ```
376
379
 
377
- ## Otherwise Operator (`!?`)
380
+ ## Otherwise Operator (`!?`) and Defined Check (`!?`)
378
381
 
379
- Handles both null/undefined AND thrown errors:
382
+ The `!?` token serves two roles, distinguished by spacing:
383
+
384
+ **Infix (spaced) — otherwise:** Provides a fallback when a value is `undefined`:
385
+
386
+ ```coffee
387
+ result = getValue() !? "default"
388
+ # If getValue() returns undefined, result = "default"
389
+ # null, 0, false, "" all pass through unchanged
390
+ ```
391
+
392
+ **Postfix (unspaced) — defined check:** Returns `true` if a value is not `undefined`:
380
393
 
381
394
  ```coffee
382
- result = riskyOperation() !? "default"
383
- # If riskyOperation() throws or returns null/undefined, result = "default"
395
+ value!? # (value !== undefined)
396
+
397
+ # Useful in comprehensions for filtering defined keys
398
+ keys = (k for k, v of obj when v!?)
384
399
  ```
385
400
 
401
+ The postfix form mirrors `?` (existence check) but with tighter semantics:
402
+
403
+ | Operator | Checks | `null` | `undefined` | `0` | `false` | `""` |
404
+ |----------|--------|--------|-------------|-----|---------|------|
405
+ | `v?` | not nullish | false | false | true | true | true |
406
+ | `v!?` | not undefined | true | false | true | true | true |
407
+
386
408
  ## Method Assignment (`.=`)
387
409
 
388
410
  A Rip original. Compound assignment for method calls — apply a method to a
@@ -1520,7 +1542,73 @@ const { code } = compile('x = 42');
1520
1542
 
1521
1543
  ---
1522
1544
 
1523
- # 14. Common Patterns
1545
+ # 14. Standard Library
1546
+
1547
+ Rip includes 13 global helper functions, always available in every compiled
1548
+ program. They're injected via `globalThis` using `??=`, so they won't override
1549
+ existing definitions and can be shadowed by redeclaring locally.
1550
+
1551
+ ## Functions
1552
+
1553
+ | Function | Description | Example |
1554
+ |----------|-------------|---------|
1555
+ | `abort(msg?)` | Log to stderr, exit with code 1 | `abort "fatal error"` |
1556
+ | `assert(v, msg?)` | Throw if falsy | `assert x > 0, "positive"` |
1557
+ | `exit(code?)` | Exit process | `exit 1` |
1558
+ | `kind(v)` | Lowercase type name (fixes `typeof`) | `kind [] # "array"` |
1559
+ | `noop()` | No-op function | `onClick ?= noop` |
1560
+ | `p(...args)` | `console.log` shorthand | `p "hello"` |
1561
+ | `pp(v)` | Pretty-print JSON, returns value | `pp user` |
1562
+ | `raise(a, b?)` | Throw error | `raise TypeError, "bad"` |
1563
+ | `rand(a?, b?)` | Random number | `rand 5, 10 # 5-10` |
1564
+ | `sleep(ms)` | Promise-based delay | `sleep! 1000` |
1565
+ | `todo(msg?)` | Throw "Not implemented" | `todo "finish later"` |
1566
+ | `warn(...args)` | `console.warn` shorthand | `warn "deprecated"` |
1567
+ | `zip(...arrays)` | Zip arrays pairwise | `zip names, ages` |
1568
+
1569
+ ## Usage
1570
+
1571
+ ```coffee
1572
+ # Output
1573
+ p "hello" # prints to stdout
1574
+ pp {name: "Alice", age: 30} # pretty-print, returns value
1575
+ warn "this API is deprecated" # prints to stderr
1576
+
1577
+ # Errors
1578
+ assert users.length > 0, "no users found"
1579
+ raise TypeError, "expected a string"
1580
+ todo "implement caching"
1581
+
1582
+ # Utilities
1583
+ kind [1, 2, 3] # "array" (not "object" like typeof)
1584
+ kind null # "null" (not "object" like typeof)
1585
+ rand 10 # integer 0-9
1586
+ rand 5, 10 # integer 5-10 inclusive
1587
+ rand # float 0.0-1.0
1588
+
1589
+ # Async (using the ! dammit operator)
1590
+ sleep! 1000 # pause for 1 second
1591
+ data = fetch! url # await fetch
1592
+
1593
+ # Control
1594
+ exit # exit with code 0
1595
+ abort "something went wrong" # log error + exit(1)
1596
+ onClick ?= noop # default no-op handler
1597
+
1598
+ # Combine arrays
1599
+ zip ["alice", "bob"], [30, 25] # [["alice", 30], ["bob", 25]]
1600
+ ```
1601
+
1602
+ ## Design
1603
+
1604
+ - **Always available** — injected into every compiled file, the CLI REPL, and the browser REPL
1605
+ - **Overridable** — `globalThis.p ??=` means your own `p = myLogger` shadows it cleanly
1606
+ - **Idempotent** — safe in multi-file apps; `??=` only sets if not already defined
1607
+ - **Inspired by** Ruby (`p`, `pp`, `raise`, `rand`), Rust (`assert`, `todo`), Python (`zip`), Kotlin (`todo`)
1608
+
1609
+ ---
1610
+
1611
+ # 15. Common Patterns
1524
1612
 
1525
1613
  ## Error Handling
1526
1614
 
@@ -1536,6 +1624,9 @@ finally
1536
1624
  # Otherwise operator for defaults
1537
1625
  value = riskyOperation() !? "default"
1538
1626
 
1627
+ # Defined check for filtering
1628
+ keys = (k for k, v of data when v!?)
1629
+
1539
1630
  # Optional chaining for safety
1540
1631
  name = user?.profile?.name ?? "Anonymous"
1541
1632
  ```
@@ -1602,7 +1693,7 @@ class EventEmitter
1602
1693
 
1603
1694
  ---
1604
1695
 
1605
- # 15. Quick Reference
1696
+ # 16. Quick Reference
1606
1697
 
1607
1698
  ## Syntax Cheat Sheet
1608
1699
 
@@ -1643,7 +1734,8 @@ X.new(a: 1)
1643
1734
 
1644
1735
  # Operators
1645
1736
  a! # await a()
1646
- a !? b # a if defined, else b
1737
+ a !? b # a if defined, else b (infix otherwise)
1738
+ a!? # true if a is defined (postfix defined check)
1647
1739
  a // b # floor divide
1648
1740
  a %% b # true modulo
1649
1741
  a =~ /pat/ # regex match, captures in _
@@ -1698,67 +1790,11 @@ count = 10 # Logs: "Count: 10, Doubled: 20"
1698
1790
 
1699
1791
  ---
1700
1792
 
1701
- # 16. Future Ideas
1793
+ # 17. Future Ideas
1702
1794
 
1703
1795
  Ideas and candidates that have been discussed but not yet implemented.
1704
1796
 
1705
- ## Standard Library (`stdlib`)
1706
-
1707
- Rip is a zero-dependency language, but a small standard library of useful
1708
- utilities would save users from writing the same one-liners in every project.
1709
- These are **not** language features — they're plain functions that could ship
1710
- as a prelude or optional import.
1711
-
1712
- ### Candidates
1713
-
1714
- ```coffee
1715
- # Printing (Ruby's p)
1716
- p = console.log
1717
-
1718
- # Exit with optional code (uses implicit `it`)
1719
- exit = -> process.exit(it)
1720
-
1721
- # Tap — call a function for side effects, return the original value
1722
- # Useful in pipe chains: data |> tap(console.log) |> process
1723
- tap = (v, fn) -> fn(v); v
1724
-
1725
- # Identity — returns its argument unchanged
1726
- # Useful as a default callback: items.filter(id)
1727
- id = -> it
1728
-
1729
- # No-op — does nothing
1730
- # Useful as a default handler: onClick ?= noop
1731
- noop = ->
1732
-
1733
- # String method aliases (shorter names for common checks)
1734
- String::starts = String::startsWith
1735
- String::ends = String::endsWith
1736
- String::has = String::includes
1737
-
1738
- # Clamp a value to a range
1739
- clamp = (v, lo, hi) -> Math.min(Math.max(v, lo), hi)
1740
-
1741
- # Sleep for N milliseconds (returns a Promise)
1742
- sleep = (ms) -> new Promise (resolve) -> setTimeout resolve, ms
1743
-
1744
- # Times helper — call a function N times, collect results
1745
- times = (n, fn) -> (fn(i) for i in [0...n])
1746
- ```
1747
-
1748
- ### Design Questions
1749
-
1750
- - **Prelude vs import?** Should these be injected automatically (like Go's
1751
- `fmt` or Rip's reactive runtime), or explicitly imported (`import { p, tap }
1752
- from '@rip-lang/std'`)? Leaning toward explicit — Rip's philosophy is zero
1753
- magic in the output.
1754
-
1755
- - **Scope?** Keep it tiny. A stdlib that grows to 500 functions defeats the
1756
- purpose. Each entry should save real keystrokes on something people do
1757
- constantly.
1758
-
1759
- - **Node vs Browser?** Some helpers (like `exit`) are Node-only. Others (like
1760
- `p`, `tap`, `sleep`) work everywhere. May want to split into `std` (universal)
1761
- and `std/node` (server-only).
1797
+ > **Note:** The standard library was implemented in v3.13.0. See [Section 14](#14-standard-library).
1762
1798
 
1763
1799
  ## Future Syntax Ideas
1764
1800
 
@@ -1792,8 +1828,8 @@ Each would need design discussion before building.
1792
1828
  | **[RIP-LANG.md](RIP-LANG.md)** | Full language reference (this file) |
1793
1829
  | **[RIP-TYPES.md](RIP-TYPES.md)** | Type system specification |
1794
1830
  | **[RIP-INTERNALS.md](RIP-INTERNALS.md)** | Compiler architecture & design decisions |
1795
- | **[AGENT.md](../AGENT.md)** | AI agent guide for working on the compiler |
1831
+ | **[AGENTS.md](../AGENTS.md)** | AI agent guide for working on the compiler |
1796
1832
 
1797
1833
  ---
1798
1834
 
1799
- *Rip 3.10 — 1,243 tests — Zero dependencies — Self-hosting — ~13,500 LOC*
1835
+ *Rip 3.13 — 1,244 tests — Zero dependencies — Self-hosting — ~13,500 LOC*