rip-lang 3.13.3 → 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 +28 -4
- package/bin/rip +6 -0
- package/docs/RIP-INTERNALS.md +13 -0
- package/docs/RIP-LANG.md +107 -71
- package/docs/dist/rip.min.js +125 -125
- package/docs/dist/rip.min.js.br +0 -0
- package/docs/index.html +14 -3
- package/package.json +1 -1
- package/rip-loader.js +10 -0
- package/src/app.rip +0 -2
- package/src/browser.js +4 -3
- package/src/compiler.js +5 -0
- package/src/grammar/grammar.rip +4 -1
- package/src/lexer.js +4 -1
- package/src/parser.js +28 -27
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<p align="center">
|
|
12
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%
|
|
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 #
|
|
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
|
-
| [
|
|
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
|
|
package/docs/RIP-INTERNALS.md
CHANGED
|
@@ -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. [
|
|
25
|
-
15. [
|
|
26
|
-
16. [
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
383
|
-
|
|
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.
|
|
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
|
-
#
|
|
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
|
-
#
|
|
1793
|
+
# 17. Future Ideas
|
|
1702
1794
|
|
|
1703
1795
|
Ideas and candidates that have been discussed but not yet implemented.
|
|
1704
1796
|
|
|
1705
|
-
|
|
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
|
-
| **[
|
|
1831
|
+
| **[AGENTS.md](../AGENTS.md)** | AI agent guide for working on the compiler |
|
|
1796
1832
|
|
|
1797
1833
|
---
|
|
1798
1834
|
|
|
1799
|
-
*Rip 3.
|
|
1835
|
+
*Rip 3.13 — 1,244 tests — Zero dependencies — Self-hosting — ~13,500 LOC*
|