Package Exports
- rip-lang
- rip-lang/loader
Readme
Rip
A modern language that compiles to JavaScript
Rip is a modern language inspired by CoffeeScript. It compiles to ES2022 (classes, ?., ??, modules), adds about a dozen new operators, includes built-in reactivity, and sports a self-hosting compiler with zero dependencies — all in about 10,000 lines of code.
No imports. No hooks. No dependency arrays. Just write code.
data = fetchUsers! # Dammit operator (call + await)
user = User.new name: "Alice" # Ruby-style constructor
squares = (x * x for x in [1..10]) # List comprehension
str =~ /Hello, (\w+)/ # Regex match
log "Found: #{_[1]}" # Captures in _[1], _[2], etc.What makes Rip different:
- Modern output — ES2022 with native classes,
?.,??, modules - New operators —
!,!?,//,%%,=~,.new(), and more - Reactive operators —
:=,~=,~>as language syntax - Zero dependencies — everything included, even the parser generator
- Self-hosting —
bun run parserrebuilds the compiler from source
Installation
bun add -g rip-lang # Install globallyrip # Interactive REPL
rip file.rip # Run a file
rip -c file.rip # Compile to JavaScriptLanguage
Functions & Classes
def greet(name) # Named function
"Hello, #{name}!"
add = (a, b) -> a + b # Arrow function
handler = (e) => @process e # Fat arrow (preserves this)
class Dog extends Animal
speak: -> log "#{@name} barks"
dog = Dog.new("Buddy") # Ruby-style constructorDestructuring & Comprehensions
{name, age} = person
[first, ...rest] = items
squares = (x * x for x in [1..10]) # Array comprehension
console.log x for x in items # Loop (no array)Async & Chaining
def loadUser(id)
response = await fetch "/api/#{id}"
await response.json()
user?.profile?.name # Optional chaining
data = fetchData! # Await shorthandReactivity
State, computed values, and effects as language operators:
| Operator | Mnemonic | Example | What it does |
|---|---|---|---|
= |
"gets value" | x = 5 |
Regular assignment |
:= |
"has state" | count := 0 |
Reactive state container |
~= |
"always equals" | twice ~= count * 2 |
Auto-updates on changes |
~> |
"reacts to" | ~> log count |
Runs on dependency changes |
=! |
"equals, dammit!" | MAX =! 100 |
Readonly constant |
New Operators
| Operator | Example | What it does |
|---|---|---|
! (dammit) |
fetchData! |
Calls AND awaits |
! (void) |
def process! |
Suppresses implicit return |
!? (otherwise) |
val !? 5 |
Default only if undefined |
?: (ternary) |
x > 0 ? 'yes' : 'no' |
JS-style ternary expression |
?. ?[] |
a?.b a?[0] |
Optional chaining (both styles) |
... (spread) |
[...rest, last] |
Spread at start or end (JS: end only) |
// |
7 // 2 |
Floor division → 3 |
%% |
-1 %% 3 |
True modulo → 2 |
=~ |
str =~ /Hello, (\w+)/ |
Match (captures in _) |
[//, n] |
str[/Hello, (\w+)/, 1] |
Extract capture n |
.new() |
Dog.new() |
Ruby-style constructor |
Optional chaining — Both CoffeeScript and ES6 styles are supported:
| Syntax | Style | Compiles to |
|---|---|---|
obj?[0] |
CoffeeScript | (obj != null ? obj[0] : undefined) |
fn?(arg) |
CoffeeScript | (typeof fn === 'function' ? fn(arg) : undefined) |
obj?.[0] |
ES6/JS | obj?.[0] |
fn?.(arg) |
ES6/JS | fn?.(arg) |
Heredoc & Heregex
Heredoc — The closing ''' position sets the left margin (smart dedent):
html = '''
<div>
<p>Hello</p>
</div>
'''
# Result: " <div>\n <p>Hello</p>\n </div>" (note the leading 2 spaces)Heregex — Extended regex with comments and whitespace:
pattern = ///
^(\d{3}) # area code
-(\d{4}) # number
///vs React / Vue / Solid
| Concept | React | Vue | Solid | Rip |
|---|---|---|---|---|
| State | useState() |
ref() |
createSignal() |
x := 0 |
| Computed | useMemo() |
computed() |
createMemo() |
x ~= y * 2 |
| Effect | useEffect() |
watch() |
createEffect() |
~> body |
Rip's reactivity is framework-agnostic — use it with React, Vue, Svelte, or vanilla JS.
vs CoffeeScript
| Feature | CoffeeScript | Rip |
|---|---|---|
| Output | ES5 (var, prototypes) | ES2022 (classes, ?., ??) |
| Reactivity | None | Built-in |
| Dependencies | Multiple | Zero |
| Self-hosting | No | Yes |
| Lexer | 3,558 LOC | 3,250 LOC |
| Compiler | 10,346 LOC | 5,878 LOC |
| Total | 17,760 LOC | ~10,100 LOC |
Smaller codebase, modern output, built-in reactivity.
Browser
Run Rip directly in the browser (51KB compressed):
<script src="https://shreeve.github.io/rip-lang/docs/dist/rip.browser.min.js"></script>
<script type="text/rip">
def greet(name)
console.log "Hello, #{name}!"
greet "World"
</script>Try it live: shreeve.github.io/rip-lang
Architecture
Source → Lexer → Parser → S-Expressions → Codegen → JavaScript
["=", "x", 42]Simple arrays instead of AST node classes. The compiler is self-hosting — bun run parser rebuilds from source.
The Rip Stack
Rip includes optional packages for full-stack development:
| Package | Purpose | Lines |
|---|---|---|
| @rip-lang/api | Web framework (Sinatra-style) | ~595 |
| @rip-lang/server | Multi-worker process manager | ~1,110 |
| @rip-lang/db | DuckDB HTTP server | ~225 |
| @rip-lang/schema | ORM with reactive models | ~420 |
bun add @rip-lang/api @rip-lang/serverImplicit Commas
Rip rescues what would be invalid syntax and gives it elegant meaning. When a literal value is followed directly by an arrow function, Rip inserts the comma for you:
# Clean route handlers (no comma needed!)
get '/users' -> User.all!
get '/users/:id' -> User.find params.id
post '/users' -> User.create body
# Works with all literal types
handle 404 -> { error: 'Not found' }
match /^\/api/ -> { version: 'v1' }
check true -> enable()This works because '/users' -> was previously a syntax error — there's no valid interpretation. Rip detects this pattern and transforms it into '/users', ->, giving dead syntax a beautiful new life.
Supported literals: strings, numbers, regex, booleans, null, undefined, arrays, objects
Quick Reference
rip # REPL
rip file.rip # Run
rip -c file.rip # Compile
rip -t file.rip # Tokens
rip -s file.rip # S-expressions
bun run test # 1021 tests
bun run parser # Rebuild parser
bun run browser # Build browser bundleDocumentation
| Guide | Description |
|---|---|
| docs/GUIDE.md | Language guide |
| docs/REACTIVITY.md | Reactivity deep dive |
| docs/INTERNALS.md | Compiler architecture |
| packages/ | Full-stack packages |
| CONTRIBUTING.md | How to contribute |
Zero Dependencies
{ "dependencies": {} }Everything included: compiler, parser generator, REPL, browser bundle, test framework.
Philosophy
Simplicity scales.
Simple IR (S-expressions), clear pipeline (lex → parse → generate), minimal code, comprehensive tests.
Inspired by: CoffeeScript, Lisp, Ruby | Powered by: Bun
MIT License
Start simple. Build incrementally. Ship elegantly.