rip-lang 3.0.2 → 3.2.0

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 CHANGED
@@ -7,6 +7,49 @@ All notable changes to Rip will be documented in this file.
7
7
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
8
8
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
9
9
 
10
+ ## [3.1.0] - 2026-02-08
11
+
12
+ ### Rip UI — Zero-Build Reactive Web Framework
13
+
14
+ Rip UI inverts the traditional web development model. Instead of building and
15
+ bundling on the server, the 40KB Rip compiler ships to the browser. Components
16
+ are delivered as `.rip` source files, stored in a browser-local Virtual File
17
+ System, compiled on demand, and rendered with fine-grained DOM updates.
18
+
19
+ **Component model** — Two keywords added to the language:
20
+
21
+ - `component` — Declares an anonymous ES6 class with reactive props, computed
22
+ values, effects, methods, and lifecycle hooks. `@` properties become props
23
+ the parent can set. `:=` creates signals, `~=` creates computed values.
24
+ - `render` — Defines a Pug/Jade-like template DSL inside a component. Tags are
25
+ bare identifiers, classes use dot notation (`div.card.active`), dynamic
26
+ classes use dot-parens (`div.("active" if @selected)`), and events use
27
+ `@click: handler`. No virtual DOM — each reactive binding creates a direct
28
+ effect that updates exactly the DOM nodes it touches.
29
+
30
+ **Framework modules** (all in `packages/ui/`):
31
+
32
+ | Module | Role |
33
+ |--------|------|
34
+ | `stash.js` | Deep reactive state tree with path-based navigation |
35
+ | `vfs.js` | Browser-local Virtual File System with file watchers |
36
+ | `router.js` | File-based router (URL ↔ VFS paths, History API) |
37
+ | `renderer.js` | Component lifecycle, layout nesting, route transitions |
38
+ | `ui.js` | `createApp` entry point, re-exports everything |
39
+
40
+ **Compiler changes** (`src/components.js`, 1,193 lines):
41
+
42
+ - 22 methods installed on `CodeGenerator.prototype` via `installComponentSupport()`
43
+ - Categorizes component body into state, computed, readonly, methods, effects, render
44
+ - Emits fine-grained DOM creation (`_create()`) and reactive setup (`_setup()`)
45
+ - Block factories for conditionals and loops with disposable effects
46
+ - Keyed reconciliation for list rendering
47
+ - Component runtime emitted only when `component` keyword is used
48
+
49
+ **No build step. No bundler. No configuration files.**
50
+
51
+ ---
52
+
10
53
  ## [3.0.0] - 2026-02-07
11
54
 
12
55
  ### Complete Rewrite
@@ -27,8 +70,6 @@ Rip 3.0 is a ground-up rewrite of the entire compiler pipeline. Every component
27
70
  #### Removed
28
71
  - Postfix spread/rest (`x...`) — use prefix `...x` (ES6)
29
72
  - Prototype access (`x::y`, `x?::y`) — use `.prototype` or class syntax
30
- - Soak call sugar (`fn?(arg)`) — use `fn?.(arg)` (ES6 optional call)
31
- - Soak index sugar (`arr?[i]`) — use `arr?.[i]` (ES6 optional index)
32
73
  - `is not` contraction — use `isnt`
33
74
 
34
75
  #### Added
@@ -37,15 +78,16 @@ Rip 3.0 is a ground-up rewrite of the entire compiler pipeline. Every component
37
78
  - `as!` async shorthand: `for x as! iterable` (shorthand for `for await x as iterable`)
38
79
 
39
80
  #### Kept
40
- - Existence check: `x?` compiles to `(x != null)`
81
+ - Existence check: `x?` compiles to `(x != null)` — works after any expression
41
82
  - All reactive operators: `:=`, `~=`, `~>`, `=!`
42
83
  - Dammit operator: `x!` compiles to `await x()`
43
- - Optional chaining: `?.`, `?.[]`, `?.()` (ES6 dot-form only)
84
+ - Optional chaining: `?.`, `?.[]`, `?.()` (ES6 dot-form)
85
+ - Optional chaining shorthand: `?[]`, `?()` (compiles to `?.[]`, `?.()`)
44
86
  - Nullish coalescing: `??`
45
87
  - All other CoffeeScript-compatible syntax
46
88
 
47
89
  ### Tests
48
- - 1,048 tests passing (100%)
90
+ - 1,073 tests passing (100%)
49
91
  - 25 test files across all language features
50
92
 
51
93
  ---
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.0.0-blue.svg" alt="Version"></a>
12
+ <a href="CHANGELOG.md"><img src="https://img.shields.io/badge/version-3.1.0-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-1048%2F1048-brightgreen.svg" alt="Tests"></a>
14
+ <a href="#"><img src="https://img.shields.io/badge/tests-1130%2F1130-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
 
@@ -40,6 +40,7 @@ get '/users/:id' -> # RESTful API endpoint, comma-less
40
40
  - **Modern output** — ES2022 with native classes, `?.`, `??`, modules
41
41
  - **New operators** — `!`, `!?`, `//`, `%%`, `=~`, `.new()`, and more
42
42
  - **Reactive operators** — `:=`, `~=`, `~>` as language syntax
43
+ - **Optional types** — `::` annotations, `::=` aliases, `.d.ts` emission
43
44
  - **Zero dependencies** — everything included, even the parser generator
44
45
  - **Self-hosting** — `bun run parser` rebuilds the compiler from source
45
46
 
@@ -137,6 +138,27 @@ State, computed values, and effects as language operators:
137
138
 
138
139
  ---
139
140
 
141
+ ### Types (Optional)
142
+
143
+ Type annotations are erased at compile time — zero runtime cost:
144
+
145
+ ```coffee
146
+ def greet(name:: string):: string # Typed function
147
+ "Hello, #{name}!"
148
+
149
+ User ::= type # Structural type
150
+ id: number
151
+ name: string
152
+
153
+ enum HttpCode # Runtime enum
154
+ ok = 200
155
+ notFound = 404
156
+ ```
157
+
158
+ 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).
159
+
160
+ ---
161
+
140
162
  ## Operators
141
163
 
142
164
  | Operator | Example | What it does |
@@ -147,6 +169,7 @@ State, computed values, and effects as language operators:
147
169
  | `?` (existence) | `x?` | True if `x != null` |
148
170
  | `?:` (ternary) | `x > 0 ? 'yes' : 'no'` | JS-style ternary expression |
149
171
  | `?.` `?.[]` `?.()` | `a?.b` `a?.[0]` `a?.()` | Optional chaining (ES6) |
172
+ | `?[]` `?()` | `a?[0]` `a?(x)` | Optional chaining shorthand |
150
173
  | `??` | `a ?? b` | Nullish coalescing |
151
174
  | `...` (spread) | `[...items, last]` | Prefix spread (ES6) |
152
175
  | `//` | `7 // 2` | Floor division |
@@ -202,6 +225,27 @@ Rip's reactivity is framework-agnostic — use it with React, Vue, Svelte, or va
202
225
 
203
226
  ---
204
227
 
228
+ ## Rip UI
229
+
230
+ Ship the 40KB Rip compiler to the browser. Components are `.rip` source files, compiled on demand, rendered with fine-grained reactivity. No build step. No bundler.
231
+
232
+ ```coffee
233
+ Counter = component
234
+ @count := 0
235
+
236
+ render
237
+ div.counter
238
+ h1 "Count: #{@count}"
239
+ button @click: (-> @count++), "+"
240
+ button @click: (-> @count--), "-"
241
+ ```
242
+
243
+ Two keywords — `component` and `render` — are all the language adds. Everything else (`:=` state, `~=` computed, methods, lifecycle) is standard Rip.
244
+
245
+ See [@rip-lang/ui](packages/ui/) for the full framework: Virtual File System, file-based router, reactive stash, and component renderer.
246
+
247
+ ---
248
+
205
249
  ## vs CoffeeScript
206
250
 
207
251
  | Feature | CoffeeScript | Rip |
@@ -263,6 +307,7 @@ Rip includes optional packages for full-stack development:
263
307
 
264
308
  | Package | Purpose | Lines |
265
309
  |---------|---------|-------|
310
+ | [@rip-lang/ui](packages/ui/) | Zero-build reactive web framework (VFS, router, components) | ~1,300 |
266
311
  | [@rip-lang/api](packages/api/) | HTTP framework (Sinatra-style routing, 37 validators) | ~1,050 |
267
312
  | [@rip-lang/server](packages/server/) | Multi-worker app server (hot reload, HTTPS, mDNS) | ~1,210 |
268
313
  | [@rip-lang/db](packages/db/) | DuckDB server with official UI (pure Bun FFI) | ~1,740 |
@@ -303,7 +348,7 @@ rip file.rip # Run
303
348
  rip -c file.rip # Compile
304
349
  rip -t file.rip # Tokens
305
350
  rip -s file.rip # S-expressions
306
- bun run test # 1048 tests
351
+ bun run test # 1073 tests
307
352
  bun run parser # Rebuild parser
308
353
  bun run browser # Build browser bundle
309
354
  ```
package/bin/rip CHANGED
@@ -25,6 +25,7 @@ Usage:
25
25
 
26
26
  Options:
27
27
  -c, --compile Show compiled JavaScript output
28
+ -d, --dts Generate .d.ts type declaration file
28
29
  -h, --help Show this help message
29
30
  -o, --output <file> Write JavaScript to file
30
31
  -q, --quiet Suppress headers
@@ -44,6 +45,8 @@ Examples:
44
45
  rip -t example.rip # Show ONLY tokens
45
46
  rip -s -c example.rip # Show s-expressions AND JavaScript
46
47
  rip -s -t -c example.rip # Show everything (full debug mode)
48
+ rip -d example.rip # Generate example.d.ts
49
+ rip -cd example.rip # Compile JS and generate .d.ts
47
50
  rip -q -c example.rip # Just the JS, no headers (for piping)
48
51
  rip -w # Launch browser REPL (auto-opens)
49
52
  echo 'x = 1 + 2' | rip -c # Compile from stdin
@@ -142,12 +145,14 @@ async function main() {
142
145
  const showTokens = ripOptions.includes('-t') || ripOptions.includes('--tokens');
143
146
  const showSExpr = ripOptions.includes('-s') || ripOptions.includes('--sexpr');
144
147
  const showCompiled = ripOptions.includes('-c') || ripOptions.includes('--compile');
148
+ const generateDts = ripOptions.includes('-d') || ripOptions.includes('--dts');
145
149
  const quiet = ripOptions.includes('-q') || ripOptions.includes('--quiet');
146
150
 
147
151
  const options = {
148
152
  showTokens,
149
153
  showSExpr,
150
- quiet
154
+ quiet,
155
+ types: generateDts ? 'emit' : undefined
151
156
  };
152
157
 
153
158
  // Find input file and output file from ripOptions only
@@ -162,7 +167,7 @@ async function main() {
162
167
  }
163
168
 
164
169
  // If .rip file without compile flags → execute instead of compile
165
- const hasCompileFlag = showCompiled || showTokens || showSExpr || outputFile;
170
+ const hasCompileFlag = showCompiled || showTokens || showSExpr || generateDts || outputFile;
166
171
  if (inputFile && inputFile.endsWith('.rip') && !hasCompileFlag) {
167
172
  // Check if file exists
168
173
  if (!existsSync(inputFile)) {
@@ -255,6 +260,23 @@ async function main() {
255
260
  }
256
261
  console.log(result.code);
257
262
  }
263
+
264
+ // Write .d.ts file
265
+ if (generateDts && result.dts) {
266
+ if (inputFile) {
267
+ let dtsFile = inputFile.replace(/\.rip$/, '.d.ts');
268
+ writeFileSync(dtsFile, result.dts, 'utf-8');
269
+ if (!options.quiet) {
270
+ console.log(`Generated ${dtsFile}`);
271
+ }
272
+ } else {
273
+ // stdin — print .d.ts to stdout
274
+ if (!options.quiet) {
275
+ console.log(`// == Type declarations == //\n`);
276
+ }
277
+ console.log(result.dts);
278
+ }
279
+ }
258
280
  } catch (error) {
259
281
  console.error('Compilation Error:', error.message);
260
282
  if (error.stack) {
@@ -63,7 +63,7 @@ console.log(code);
63
63
 
64
64
  | Feature | CoffeeScript | Rip |
65
65
  |---------|-------------|------|
66
- | Optional chaining | 4 soak operators | ES6 `?.` / `?.[]` / `?.()` |
66
+ | Optional chaining | 4 soak operators | ES6 `?.` / `?.[]` / `?.()` + shorthand `?[]` / `?()` |
67
67
  | Ternary | No | `x ? a : b` |
68
68
  | Regex features | Basic | Ruby-style (`=~`, indexing, captures in `_`) |
69
69
  | Async shorthand | No | Dammit operator (`!`) |
@@ -90,18 +90,22 @@ console.log(code);
90
90
  ## The Pipeline
91
91
 
92
92
  ```
93
- Source Code → Lexer → Parser → S-Expressions → Codegen → JavaScript
94
- (1,542) (352) (simple arrays) (3,148) (ES2022)
93
+ Source Code → Lexer → emitTypes → Parser → S-Expressions → Codegen → JavaScript
94
+ (1,866) (types.js) (356) (simple arrays) (3,219) (ES2022)
95
+
96
+ file.d.ts (when types: "emit")
95
97
  ```
96
98
 
97
99
  ## Key Files
98
100
 
99
101
  | File | Purpose | Lines | Modify? |
100
102
  |------|---------|-------|---------|
101
- | `src/lexer.js` | Lexer + Rewriter | 1,542 | Yes |
102
- | `src/compiler.js` | Compiler + Code Generator | 3,148 | Yes |
103
- | `src/parser.js` | Generated parser | 352 | No (auto-gen) |
104
- | `src/grammar/grammar.rip` | Grammar specification | 887 | Yes (carefully) |
103
+ | `src/lexer.js` | Lexer + Rewriter | 1,866 | Yes |
104
+ | `src/compiler.js` | Compiler + Code Generator | 3,219 | Yes |
105
+ | `src/types.js` | Type System (lexer sidecar) | 718 | Yes |
106
+ | `src/components.js` | Component System (compiler sidecar) | ~1,240 | Yes |
107
+ | `src/parser.js` | Generated parser | 356 | No (auto-gen) |
108
+ | `src/grammar/grammar.rip` | Grammar specification | 934 | Yes (carefully) |
105
109
  | `src/grammar/solar.rip` | Parser generator | 1,001 | No |
106
110
 
107
111
  ## Example Flow
@@ -240,6 +244,12 @@ S-expressions are simple arrays that serve as Rip's intermediate representation
240
244
  ['class', name, parent?, ...members]
241
245
  ```
242
246
 
247
+ ### Types
248
+ ```javascript
249
+ ['enum', name, body] // Enum declaration (runtime JS)
250
+ // Type aliases, interfaces → handled by rewriter, never reach parser
251
+ ```
252
+
243
253
  ### Ranges & Slicing
244
254
  ```javascript
245
255
  ['..', from, to] // Inclusive range
@@ -298,8 +308,6 @@ The `!` suffix on `as` in for-loops (`as!`) emits `FORASAWAIT` instead of `FORAS
298
308
  |---------|-----------|-------------|
299
309
  | Postfix spread/rest | `x...` | `...x` (ES6 prefix only) |
300
310
  | Prototype access | `x::y`, `x?::y` | Direct `.prototype` or class syntax; `::` reserved for type annotations |
301
- | Soak call sugar | `x?(args)` | `x?.(args)` (ES6 optional call) |
302
- | Soak index sugar | `x?[i]` | `x?.[i]` (ES6 optional index) |
303
311
  | `is not` contraction | `x is not y` | `x isnt y` |
304
312
 
305
313
  ### Added
@@ -395,13 +403,15 @@ counter = ->
395
403
  | `x ?? y` | `x ?? y` |
396
404
  | `x ??= 10` | `x ??= 10` |
397
405
 
398
- Optional chaining uses ES6 syntax:
406
+ Optional chaining uses ES6 syntax (both forms supported):
399
407
 
400
408
  | Syntax | Compiles To |
401
409
  |--------|-------------|
402
410
  | `obj?.prop` | `obj?.prop` |
403
411
  | `arr?.[0]` | `arr?.[0]` |
404
412
  | `fn?.(x)` | `fn?.(x)` |
413
+ | `arr?[0]` | `arr?.[0]` |
414
+ | `fn?(x)` | `fn?.(x)` |
405
415
 
406
416
  ## Range Optimization
407
417
 
@@ -458,8 +468,6 @@ The `Compiler` class's lexer adapter reconstructs `new String()` wrapping from t
458
468
  |-----------|--------|--------|
459
469
  | `generatePrototype` | `::` | Feature removed from lexer |
460
470
  | `generateOptionalPrototype` | `?::` | Feature removed from lexer |
461
- | `generateSoakIndex` | `?[]` | Replaced by `optindex` / `?.[]` |
462
- | `generateSoakCall` | `?call` | Replaced by `optcall` / `?.()` |
463
471
 
464
472
  ## Renamed: `for-from` → `for-as`
465
473
 
@@ -560,7 +568,6 @@ bun src/compare-compilers.js
560
568
 
561
569
  # 9. Future Work
562
570
 
563
- - `::` token for type annotations (see `docs/RIP-TYPES.md`)
564
571
  - Comment preservation for source maps
565
572
  - Parser update to read `.data` directly instead of `new String()` properties
566
573
  - Once parser supports `.data`, the `meta()`/`str()` helpers become trivial to update
@@ -569,8 +576,9 @@ bun src/compare-compilers.js
569
576
 
570
577
  **See Also:**
571
578
  - [RIP-LANG.md](RIP-LANG.md) — Language reference
579
+ - [RIP-TYPES.md](RIP-TYPES.md) — Type system specification
572
580
  - [RIP-REACTIVITY.md](RIP-REACTIVITY.md) — Reactivity deep dive
573
581
 
574
582
  ---
575
583
 
576
- *Rip 3.0.0 — 1,048 tests passing — Zero dependencies — Self-hosting — ~7,700 LOC*
584
+ *Rip 3.0 — 1,130 tests passing — Zero dependencies — Self-hosting — ~8,800 LOC*
package/docs/RIP-LANG.md CHANGED
@@ -17,9 +17,10 @@ Rip is a modern reactive language that compiles to ES2022 JavaScript. It combine
17
17
  9. [Regex Features](#9-regex-features)
18
18
  10. [Server-Side Development](#10-server-side-development)
19
19
  11. [CLI Tools & Scripts](#11-cli-tools--scripts)
20
- 12. [JavaScript Interop](#12-javascript-interop)
21
- 13. [Common Patterns](#13-common-patterns)
22
- 14. [Quick Reference](#14-quick-reference)
20
+ 12. [Types](#12-types)
21
+ 13. [JavaScript Interop](#13-javascript-interop)
22
+ 14. [Common Patterns](#14-common-patterns)
23
+ 15. [Quick Reference](#15-quick-reference)
23
24
 
24
25
  ---
25
26
 
@@ -273,6 +274,7 @@ Multiple lines
273
274
  | `?` (postfix) | `a?` | Existence check (`a != null`) |
274
275
  | `?` (ternary) | `a ? b : c` | Ternary conditional |
275
276
  | `?.` `?.[]` `?.()` | `a?.b` `a?.[0]` `a?.()` | Optional chaining (ES6) |
277
+ | `?[]` `?()` | `a?[0]` `a?(x)` | Optional chaining shorthand |
276
278
  | `??` | `a ?? b` | Nullish coalescing |
277
279
 
278
280
  ## Rip-Specific Operators
@@ -311,10 +313,14 @@ x ||= val # x = x || val
311
313
  ## Optional Chaining
312
314
 
313
315
  ```coffee
314
- # ES6 optional chaining (dot required)
316
+ # ES6 optional chaining (with dot)
315
317
  user?.profile?.name
316
318
  arr?.[0]
317
319
  fn?.(arg)
320
+
321
+ # Shorthand (without dot — same behavior)
322
+ arr?[0] # Compiles to arr?.[0]
323
+ fn?(arg) # Compiles to fn?.(arg)
318
324
  ```
319
325
 
320
326
  ## Ternary Operator
@@ -919,7 +925,41 @@ for filename in files
919
925
 
920
926
  ---
921
927
 
922
- # 12. JavaScript Interop
928
+ # 12. Types
929
+
930
+ Rip supports an optional, compile-time-only type system. Types are erased from
931
+ `.js` output and preserved in `.d.ts` declaration files.
932
+
933
+ ```coffee
934
+ # Type annotations (::)
935
+ count:: number = 0
936
+ def greet(name:: string):: string
937
+ "Hello, #{name}!"
938
+
939
+ # Type aliases (::=)
940
+ ID ::= number
941
+ User ::= type
942
+ id: number
943
+ name: string
944
+
945
+ # Interfaces
946
+ interface Animal
947
+ name: string
948
+
949
+ # Enums (emit runtime JS)
950
+ enum HttpCode
951
+ ok = 200
952
+ notFound = 404
953
+ ```
954
+
955
+ Types use `=>` for function type arrows and `->` for code arrows. This
956
+ disambiguates type expressions from function bodies cleanly.
957
+
958
+ For the complete type system specification, see [RIP-TYPES.md](RIP-TYPES.md).
959
+
960
+ ---
961
+
962
+ # 13. JavaScript Interop
923
963
 
924
964
  ```coffee
925
965
  # Import any npm package
@@ -949,7 +989,7 @@ const { code } = compile('x = 42');
949
989
 
950
990
  ---
951
991
 
952
- # 13. Common Patterns
992
+ # 14. Common Patterns
953
993
 
954
994
  ## Error Handling
955
995
 
@@ -1031,7 +1071,7 @@ class EventEmitter
1031
1071
 
1032
1072
  ---
1033
1073
 
1034
- # 14. Quick Reference
1074
+ # 15. Quick Reference
1035
1075
 
1036
1076
  ## Syntax Cheat Sheet
1037
1077
 
@@ -1123,4 +1163,4 @@ count = 10 # Logs: "Count: 10, Doubled: 20"
1123
1163
 
1124
1164
  ---
1125
1165
 
1126
- *Rip 3.0.0 — 1,048 tests passing — Zero dependencies — Self-hosting — ~7,700 LOC*
1166
+ *Rip 3.0 — 1,073 tests passing — Zero dependencies — Self-hosting — ~7,700 LOC*
@@ -286,3 +286,24 @@ Rip's reactivity system:
286
286
  ✅ **Extra utilities** — `.lock()`, `.read()`, `.kill()` that others lack <br>
287
287
 
288
288
  **On par with Vue/Solid. Better than React. A fraction of the size.**
289
+
290
+ ---
291
+
292
+ ## Types and Reactivity
293
+
294
+ Reactive operators work with Rip's optional type system:
295
+
296
+ ```coffee
297
+ count:: number := 0 # Typed state
298
+ doubled:: number ~= count * 2 # Typed computed
299
+ ```
300
+
301
+ Type annotations are erased from `.js` output. In `.d.ts` output, reactive
302
+ state emits `Signal<T>` and computed values emit `Computed<T>`:
303
+
304
+ ```ts
305
+ declare const count: Signal<number>;
306
+ declare const doubled: Computed<number>;
307
+ ```
308
+
309
+ See [RIP-TYPES.md](RIP-TYPES.md) for the complete type system specification.