rip-lang 3.13.134 → 3.13.136

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
@@ -115,7 +115,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
115
115
  | `@rip-lang/x12` | 0.2.7 |
116
116
  | `@rip-lang/schema` | 0.3.7 |
117
117
  | `@rip-lang/swarm` | 1.2.17 |
118
- | `@rip-lang/all` | 3.13.26 |
119
118
 
120
119
  ## [3.10.12] - 2026-02-20
121
120
 
package/README.md CHANGED
@@ -9,7 +9,7 @@
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.134-blue.svg" alt="Version"></a>
12
+ <a href="CHANGELOG.md"><img src="https://img.shields.io/badge/version-3.13.136-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
14
  <a href="#"><img src="https://img.shields.io/badge/tests-1%2C436%2F1%2C436-brightgreen.svg" alt="Tests"></a>
15
15
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg" alt="License"></a>
@@ -38,7 +38,7 @@ get '/users/:id' -> # RESTful API endpoint, comma-less
38
38
 
39
39
  **What makes Rip different:**
40
40
  - **Modern output** — ES2022 with native classes, `?.`, `??`, modules
41
- - **New operators** — `!`, `!?`, `//`, `%%`, `=~`, `|>`, `.new()`, and more
41
+ - **New operators** — `!`, `//`, `%%`, `=~`, `|>`, `.new()`, and more
42
42
  - **Reactive operators** — `:=`, `~=`, `~>` as language syntax
43
43
  - **Optional types** — `::` annotations, `type` aliases, `.d.ts` emission
44
44
  - **Zero dependencies** — everything included, even the parser generator
@@ -213,8 +213,6 @@ All use `globalThis` with `??=` — override any by redeclaring locally.
213
213
  |----------|---------|--------------|
214
214
  | `!` (dammit) | `fetchData!` | Calls AND awaits |
215
215
  | `!` (void) | `def process!` | Suppresses implicit return |
216
- | `!?` (otherwise) | `val !? 5` | Default only if `undefined` (infix) |
217
- | `!?` (defined) | `val!?` | True if not `undefined` (postfix) |
218
216
  | `?!` (presence) | `@checked?!` | True if truthy, else `undefined` (Houdini operator) |
219
217
  | `?` (existence) | `x?` | True if `x != null` |
220
218
  | `?:` (ternary) | `x > 0 ? 'yes' : 'no'` | JS-style ternary expression |
@@ -485,7 +483,7 @@ bun run build # Build browser bundle
485
483
  ## Release
486
484
 
487
485
  ```bash
488
- # rip-lang + changed @rip-lang/* packages + @rip-lang/all
486
+ # rip-lang + changed @rip-lang/* packages
489
487
  bun run bump
490
488
 
491
489
  # Explicit version level
@@ -495,8 +493,7 @@ bun run bump major
495
493
  ```
496
494
 
497
495
  - `bun run bump` is the standard release flow for the npm ecosystem in this repo.
498
- - It bumps `rip-lang`, bumps any changed publishable `@rip-lang/*` packages, updates `@rip-lang/all`, runs the build and test steps, then commits, pushes, and publishes.
499
- - `@rip-lang/all` is released automatically as part of that flow; there is no separate manual release step for it.
496
+ - It bumps `rip-lang`, bumps any changed publishable `@rip-lang/*` packages, runs the build and test steps, then commits, pushes, and publishes.
500
497
  - `packages/vscode` is intentionally excluded and must be versioned and published separately.
501
498
 
502
499
  ---
package/bin/rip CHANGED
@@ -4,7 +4,7 @@ import { readFileSync, readdirSync, writeFileSync, existsSync, statSync, unlinkS
4
4
  import { execSync, spawnSync, spawn } from 'child_process';
5
5
  import { fileURLToPath } from 'url';
6
6
  import { dirname, basename, join } from 'path';
7
- import { Compiler } from '../src/compiler.js';
7
+ import { Compiler, formatError } from '../src/compiler.js';
8
8
  import packageJson from '../package.json' with { type: 'json' };
9
9
 
10
10
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -55,8 +55,13 @@ Options:
55
55
 
56
56
  Subcommands:
57
57
  rip check [dir] Type-check all .rip files in directory
58
+ rip check --strict [dir] Require type annotations in all .rip files
58
59
  rip <name> [args] Run rip-<name> (repo bin/, node_modules, or PATH)
59
60
 
61
+ Configure via rip.json or package.json:
62
+ { "strict": true, "exclude": ["scripts/**"] } # rip.json
63
+ { "rip": { "strict": true, "exclude": ["scripts/**"] } } # package.json
64
+
60
65
  Packages:
61
66
  rip server [flags] [app] Start server (run 'rip server -h' for details)
62
67
  rip db DuckDB server
@@ -135,6 +140,7 @@ async function main() {
135
140
  const dir = checkArgs.find(a => !a.startsWith('-')) || '.';
136
141
  const exitCode = await runCheck(dir, {
137
142
  quiet: checkArgs.includes('-q') || checkArgs.includes('--quiet'),
143
+ strict: checkArgs.includes('--strict'),
138
144
  });
139
145
  process.exit(exitCode);
140
146
  }
@@ -269,7 +275,13 @@ async function main() {
269
275
 
270
276
  // Non-.rip files (e.g. shebang scripts): compile to temp file, execute, clean up
271
277
  const source = readFileSync(inputFile, 'utf-8');
272
- const result = new Compiler().compile(source);
278
+ let result;
279
+ try {
280
+ result = new Compiler({ filename: inputFile }).compile(source);
281
+ } catch (error) {
282
+ console.error(formatError(error, { source, file: inputFile }));
283
+ process.exit(1);
284
+ }
273
285
  const tmp = join(dirname(inputFile), `.${basename(inputFile)}.__rip__.mjs`);
274
286
  let exitCode = 0;
275
287
  try {
@@ -295,6 +307,7 @@ async function main() {
295
307
 
296
308
  try {
297
309
  const compiler = new Compiler({ showTokens, showSExpr, quiet,
310
+ filename: inputFile || '<stdin>',
298
311
  types: generateDts ? 'emit' : undefined,
299
312
  sourceMap: generateMap ? 'inline' : undefined,
300
313
  });
@@ -320,8 +333,7 @@ async function main() {
320
333
  console.log(entry.tsContent);
321
334
  }
322
335
  } catch (error) {
323
- console.error('Compilation Error:', error.message);
324
- if (error.stack) console.error(error.stack);
336
+ console.error(formatError(error, { source, file: inputFile }));
325
337
  process.exit(1);
326
338
  }
327
339
  }
package/docs/RIP-LANG.md CHANGED
@@ -305,7 +305,6 @@ Multiple lines
305
305
  | `in` | `x in arr` | Array membership |
306
306
  | `of` | `k of obj` | Object key existence |
307
307
  | `?` (postfix) | `a?` | Existence check (`a != null`) |
308
- | `!?` (postfix) | `a!?` | Defined check (`a !== undefined`) |
309
308
  | `?!` (postfix) | `a?!` | Presence check — true if truthy, else undefined |
310
309
  | `?` (ternary) | `a ? b : c` | Ternary conditional |
311
310
  | `if...else` (postfix) | `b if a else c` | Python-style ternary |
@@ -325,8 +324,6 @@ Multiple lines
325
324
  | `%%` | True modulo | `-1 %% 3` | Always positive result (not remainder) |
326
325
  | `!` | Dammit | `fetchData!` | `await fetchData()` — calls AND awaits |
327
326
  | `!` | Void | `def process!` | Suppresses implicit return |
328
- | `!?` | Otherwise | `val !? 5` | Default if undefined (infix) |
329
- | `!?` | Defined | `val!?` | True if not undefined (postfix) |
330
327
  | `?!` | Presence | `@checked?!` | `(this.checked ? true : undefined)` — Houdini operator |
331
328
  | `=~` | Match | `str =~ /pat/` | Ruby-style regex match, captures in `_` |
332
329
  | `::` | Prototype | `String::trim` | `String.prototype.trim` |
@@ -438,37 +435,6 @@ level = score > 90 ? 'A' : score > 80 ? 'B' : score > 70 ? 'C' : 'F'
438
435
  item = found ? (arr[0]) : default
439
436
  ```
440
437
 
441
- ## Otherwise Operator (`!?`) and Defined Check (`!?`)
442
-
443
- The `!?` token serves two roles, distinguished by spacing:
444
-
445
- **Infix (spaced) — otherwise:** Provides a fallback when a value is `undefined`:
446
-
447
- ```coffee
448
- result = getValue() !? "default"
449
- # If getValue() returns undefined, result = "default"
450
- # null, 0, false, "" all pass through unchanged
451
- ```
452
-
453
- **Postfix (unspaced) — defined check:** Returns `true` if a value is not `undefined`:
454
-
455
- ```coffee
456
- value!? # (value !== undefined)
457
-
458
- # Useful in comprehensions for filtering defined keys
459
- keys = (k for k, v of obj when v!?)
460
- ```
461
-
462
- The postfix form mirrors `?` (existence check) but with tighter semantics:
463
-
464
- | Operator | Checks | `null` | `undefined` | `0` | `false` | `""` |
465
- |----------|--------|--------|-------------|-----|---------|------|
466
- | `v?` | not nullish | false | false | true | true | true |
467
- | `v!?` | not undefined | true | false | true | true | true |
468
- | `v?!` | truthy presence | `undefined` | `undefined` | `undefined` | `undefined` | `undefined` |
469
-
470
- Note: `v?!` returns `true` for truthy values, `undefined` for falsy values.
471
-
472
438
  ## Presence Operator (`?!`) — The Houdini
473
439
 
474
440
  The `?!` operator (postfix, unspaced) returns `true` if the value is truthy,
@@ -1955,12 +1921,6 @@ catch error
1955
1921
  finally
1956
1922
  cleanup()
1957
1923
 
1958
- # Otherwise operator for defaults
1959
- value = riskyOperation() !? "default"
1960
-
1961
- # Defined check for filtering
1962
- keys = (k for k, v of data when v!?)
1963
-
1964
1924
  # Optional chaining for safety
1965
1925
  name = user?.profile?.name ?? "Anonymous"
1966
1926
  ```
@@ -2068,8 +2028,6 @@ X.new(a: 1)
2068
2028
 
2069
2029
  # Operators
2070
2030
  a! # await a()
2071
- a !? b # a if defined, else b (infix otherwise)
2072
- a!? # true if a is defined (postfix defined check)
2073
2031
  a?! # true if truthy, else undefined (Houdini)
2074
2032
  a // b # floor divide
2075
2033
  a %% b # true modulo
package/docs/RIP-TYPES.md CHANGED
@@ -643,39 +643,40 @@ count = 0 # Inferred as number
643
643
 
644
644
  ## Adoption Model
645
645
 
646
- Types are optional at every level:
646
+ Types are optional and gradual:
647
647
 
648
648
  ### Project Level
649
649
 
650
+ Configure via `rip.json` or the `"rip"` key in `package.json`:
651
+
650
652
  ```json
651
653
  {
652
- "rip": {
653
- "types": "emit"
654
- }
654
+ "strict": true,
655
+ "exclude": ["vendor/**", "legacy/**"]
655
656
  }
656
657
  ```
657
658
 
658
- | Mode | Behavior |
659
- |------|----------|
660
- | `"off"` | Types are parsed but ignored no `.d.ts` emitted |
661
- | `"emit"` | `.d.ts` files generated enables editor IntelliSense |
662
- | `"check"` | `.d.ts` generated + `tsc --noEmit` validates types |
659
+ | Key | Purpose |
660
+ |-----|--------|
661
+ | `strict` | Enable strict mode for `rip check` (default: `false`) |
662
+ | `exclude` | Glob patterns for files to skip during `rip check` |
663
663
 
664
664
  ### File Level
665
665
 
666
- Override per-file with a directive:
666
+ Opt out of type-checking for a single file:
667
667
 
668
668
  ```coffee
669
- # @types off — Ignore types in this file
670
- # @types emit — Parse and emit .d.ts
671
- # @types check — Full TypeScript checking
669
+ # @nocheck
672
670
  ```
673
671
 
672
+ Untyped files (no `::` annotations) automatically get `// @ts-nocheck` —
673
+ no directive needed.
674
+
674
675
  ### Gradual Path
675
676
 
676
- 1. **Start with `"off"`** — Write normal Rip code
677
- 2. **Enable `"emit"`**Add types where helpful, get `.d.ts` for tooling
678
- 3. **Move to `"check"`** Enforce type safety via TypeScript
677
+ 1. **Start untyped** — Write normal Rip code
678
+ 2. **Add annotations**Use `::` where helpful; `.d.ts` emitted automatically for editor IntelliSense
679
+ 3. **Enable strict** — Set `strict: true` to enforce type safety via `rip check`
679
680
 
680
681
  ---
681
682
 
@@ -752,7 +753,7 @@ Rip Types is primarily **editor-driven**. The intended loop:
752
753
  1. Define shapes and contracts
753
754
  2. Annotate public boundaries
754
755
  3. Let the editor guide implementation
755
- 4. Optionally validate via `types: "check"`
756
+ 4. Optionally validate via `rip check`
756
757
 
757
758
  High-quality `.d.ts` output is a first-class goal.
758
759
 
@@ -768,8 +769,8 @@ Rip does not:
768
769
  - Introduce runtime type checks
769
770
  - Evaluate type expressions or prove soundness
770
771
 
771
- These responsibilities belong to editors, linters, and TypeScript tooling
772
- (when enabled via `types: "check"`).
772
+ These responsibilities belong to editors, linters, and TypeScript tooling.
773
+ When types are added, `rip check` delegates to TypeScript, which provides all of these capabilities.
773
774
 
774
775
  Rip only needs to:
775
776
 
@@ -821,13 +822,13 @@ Components added `src/components.js` alongside the compiler — types add
821
822
  |------|------|-------|
822
823
  | `src/lexer.js` | Detect `::` and `type` keyword, import `installTypeSupport` from `types.js` | Small inline changes |
823
824
  | `src/types.js` | Lexer sidecar: `installTypeSupport(Lexer)`, `emitTypes(tokens)`, `generateEnum()` | New file, bulk of logic |
824
- | `src/compiler.js` | Call `emitTypes()` before parsing, wire `generateEnum()` | ~8 lines |
825
+ | `src/compiler.js` | Call `emitTypes()` after codegen, wire `generateEnum()` | ~8 lines |
825
826
  | `src/grammar/grammar.rip` | Add `Enum` rule to `Expression` | 1 rule + 1 export |
826
827
 
827
828
  The boundary is the token stream. Types are fully resolved before parsing —
828
- `rewriteTypes()` strips annotations, `emitTypes()` produces `.d.ts` from
829
- annotated tokens, and type-only constructs are removed before the parser
830
- runs. Only `enum` crosses into the parser/compiler because it generates
829
+ `rewriteTypes()` strips annotations and type-only constructs are removed
830
+ before the parser runs. After codegen, `emitTypes()` produces `.d.ts` from
831
+ the annotated tokens and the parsed s-expression tree. Only `enum` crosses into the parser/compiler because it generates
831
832
  runtime JavaScript.
832
833
 
833
834
  **The `components.js` pattern to follow:**
@@ -1415,8 +1416,9 @@ if (tag === 'IDENTIFIER' && token[1] === 'type') {
1415
1416
  }
1416
1417
  ```
1417
1418
 
1418
- The `TYPE_DECL` marker is read by `emitTypes()` and then **removed from
1419
- the token stream** before parsing. No grammar rule is needed.
1419
+ The `TYPE_DECL` marker is **removed from the token stream** before parsing
1420
+ (a copy is saved for `emitTypes()`, which runs after codegen). No grammar
1421
+ rule is needed.
1420
1422
 
1421
1423
  .d.ts: `type ID = number;`
1422
1424
 
@@ -1567,9 +1569,9 @@ enum HttpCode {
1567
1569
  }
1568
1570
  ```
1569
1571
 
1570
- Note: `emitTypes()` reads enum info from the token stream before parsing.
1571
- The enum tokens remain in the stream for the parser (unlike type aliases
1572
- and interfaces, which are removed).
1572
+ Note: `emitTypes()` reads enum info from the saved token stream after
1573
+ codegen. The enum tokens remain in the stream for the parser (unlike type
1574
+ aliases and interfaces, which are removed).
1573
1575
 
1574
1576
  #### 2.5 Grammar Changes Summary
1575
1577
 
@@ -1669,9 +1671,10 @@ type Container<T> = { value: T; };
1669
1671
 
1670
1672
  ### Phase 3: Dual Emission (.js and .d.ts)
1671
1673
 
1672
- All type-related logic lives in `src/types.js`. The `.d.ts` is emitted from
1673
- the annotated token stream (before parsing). The `.js` is generated by the
1674
- existing CodeGenerator (after parsing), with only `generateEnum()` added.
1674
+ All type-related logic lives in `src/types.js`. The `.js` is generated by
1675
+ the existing CodeGenerator (after parsing), with only `generateEnum()`
1676
+ added. The `.d.ts` is emitted last by `emitTypes()`, which receives both
1677
+ the annotated token stream and the parsed s-expression tree.
1675
1678
 
1676
1679
  #### 3.1 `generateEnum()` — The One Compiler Addition
1677
1680
 
@@ -1725,13 +1728,9 @@ compile(source) {
1725
1728
  let lexer = new Lexer();
1726
1729
  let tokens = lexer.tokenize(source);
1727
1730
 
1728
- // Step 2: Emit .d.ts from annotated tokens (before parsing)
1729
- let dts = null;
1730
- if (this.options.types === 'emit' || this.options.types === 'check') {
1731
- dts = emitTypes(tokens);
1732
- // Remove TYPE_DECL markers so the parser doesn't see them
1733
- tokens = tokens.filter(t => t[0] !== 'TYPE_DECL');
1734
- }
1731
+ // Step 2: Save annotated tokens, remove TYPE_DECL markers
1732
+ let typeTokens = hasTypes ? [...tokens] : null;
1733
+ tokens = tokens.filter(t => t[0] !== 'TYPE_DECL');
1735
1734
 
1736
1735
  // Step 3: Parse (grammar only sees Enum as a new construct)
1737
1736
  // ... existing parser setup ...
@@ -1741,15 +1740,18 @@ compile(source) {
1741
1740
  let generator = new CodeGenerator({ ... });
1742
1741
  let code = generator.compile(sexpr);
1743
1742
 
1743
+ // Step 5: Emit .d.ts from annotated tokens + parsed s-expression
1744
+ let dts = typeTokens ? emitTypes(typeTokens, sexpr, source) : null;
1745
+
1744
1746
  return { tokens, sexpr, code, dts, data: dataSection, reactiveVars: generator.reactiveVars };
1745
1747
  }
1746
1748
  ```
1747
1749
 
1748
- The key insight: `.d.ts` emission happens **between tokenization and
1749
- parsing**. After `emitTypes()` reads the annotated tokens, `TYPE_DECL`
1750
- markers are filtered out. The parser receives a clean, type-free token
1751
- stream identical to what it would see from untyped Rip code, plus
1752
- `ENUM` tokens.
1750
+ The key insight: `TYPE_DECL` markers are removed **before parsing**, so the
1751
+ parser receives a clean, type-free token stream — identical to what it
1752
+ would see from untyped Rip code, plus `ENUM` tokens. The `.d.ts` is emitted
1753
+ **after codegen**, giving `emitTypes()` access to both the annotated tokens
1754
+ and the s-expression tree (used for export detection, etc.).
1753
1755
 
1754
1756
  #### 3.4 `const` Emission Rule
1755
1757
 
@@ -1791,15 +1793,8 @@ See §1.6 for the full Rip-to-TypeScript conversion table (including `::` → `:
1791
1793
 
1792
1794
  #### File-Level Type Directives
1793
1795
 
1794
- ```coffee
1795
- # @types off — ignore types in this file
1796
- # @types emit — emit .d.ts
1797
- # @types check — emit .d.ts + enable tsc validation
1798
- ```
1799
-
1800
- These are comments. The lexer's `commentToken()` method can detect this
1801
- pattern and set a flag. Alternatively, the `Compiler` can scan for the
1802
- directive before tokenizing.
1796
+ A `# @nocheck` comment near the top of a file opts it out of type-checking.
1797
+ Untyped files (no `::` annotations) are automatically skipped.
1803
1798
 
1804
1799
  #### Export of Type-Only Declarations
1805
1800
 
package/docs/demo.html CHANGED
@@ -959,10 +959,10 @@ export Dashboard = component
959
959
 
960
960
  p class: 'section', "Core Charts"
961
961
  .grid
962
- ChartCard title: 'Monthly Revenue', subtitle: 'Total recurring revenue by month', chartId: 'line'
962
+ ChartCard title: 'Monthly Revenue', subtitle: 'Total recurring revenue by month', chartId: 'line'
963
963
  ChartCard title: 'Revenue by Product', subtitle: 'Stacked area breakdown across product lines', chartId: 'area'
964
964
  ChartCard title: 'Quarterly Revenue', subtitle: 'Quarter-over-quarter comparison', chartId: 'bar'
965
- ChartCard title: 'Revenue by Product × Quarter', subtitle: 'Stacked bar with product breakdown', chartId: 'stackedBar'
965
+ ChartCard title: 'Revenue by Product × Quarter', subtitle: 'Stacked bar with product breakdown', chartId: 'stackedBar'
966
966
  ChartCard title: 'Revenue & Margin', subtitle: 'Revenue bars with gross margin % line overlay', chartId: 'combo'
967
967
  ChartCard title: 'Revenue & Growth Rate', subtitle: 'Dual-axis: revenue (left) and YoY growth (right)', chartId: 'multiAxis'
968
968
  ChartCard title: 'Customers by Segment', subtitle: 'Distribution across customer tiers', chartId: 'pie'