New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

rip-lang

Package Overview
Dependencies
Maintainers
1
Versions
230
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

rip-lang

A modern language that compiles to JavaScript

latest
Source
npmnpm
Version
3.13.134
Version published
Weekly downloads
1.7K
109.34%
Maintainers
1
Weekly downloads
 
Created
Source

Rip Logo

Rip

A modern language that compiles to JavaScript

Version Dependencies Tests License

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 11,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.

get '/users/:id' ->                 # RESTful API endpoint, comma-less
  name = read 'name', 'string!'     # Required string
  age  = read 'age' , [0, 105]      # Simple numeric validation

What makes Rip different:

  • Modern output — ES2022 with native classes, ?., ??, modules
  • New operators!, !?, //, %%, =~, |>, .new(), and more
  • Reactive operators:=, ~=, ~> as language syntax
  • Optional types:: annotations, type aliases, .d.ts emission
  • Zero dependencies — everything included, even the parser generator
  • Self-hostingbun run parser rebuilds the compiler from source

Installation

bun add -g rip-lang            # Install globally
rip                            # Interactive REPL
rip file.rip                   # Run a file
rip -c file.rip                # Compile to JavaScript

Language

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 constructor

String Interpolation

"Hello, #{name}!"              # CoffeeScript-style
"Hello, ${name}!"              # JavaScript-style
"#{a} + #{b} = #{a + b}"       # Expressions work in both

Both #{} and ${} compile to JavaScript template literals. Use whichever you prefer.

Objects

user = {name: "Alice", age: 30}
config =
  api.endpoint: "https://example.com"  # Dotted keys become flat string keys
  api.timeout: 5000                    # {'api.endpoint': "...", 'api.timeout': 5000}

Destructuring & 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
el?.scrollTop = 0              # Optional chain assignment
data = fetchData!              # Await shorthand

Iteration

for item in [1, 2, 3]         # Array iteration (for-in)
  console.log item

for key, value of object       # Object iteration (for-of)
  console.log "#{key}: #{value}"

for x as iterable              # ES6 for-of on any iterable
  console.log x

for x as! asyncIterable        # Async iteration shorthand
  console.log x                # Equivalent to: for await x as asyncIterable

loop                           # Infinite loop (while true)
  process!
loop 5                         # Repeat N times
  console.log "hi"

Implicit it

Arrow functions with no params that reference it auto-inject it as the parameter:

users.filter -> it.active          # → users.filter(function(it) { ... })
names = users.map -> it.name       # no need to name a throwaway variable
orders.filter -> it.total > 100    # works with any expression

Reactivity

State, computed values, and effects as language operators:

OperatorMnemonicExampleWhat it does
="gets value"x = 5Regular assignment
:="gets state"count := 0Reactive state container
~="always equals"twice ~= count * 2Auto-updates on changes
~>"always calls"~> log countRuns on dependency changes
=!"equals, dammit!"MAX =! 100Readonly constant

Types (Optional)

Type annotations are erased at compile time — zero runtime cost:

def greet(name:: string):: string        # Typed function
  "Hello, #{name}!"

type User =                              # Structural type
  id: number
  name: string

enum HttpCode                            # Runtime enum
  ok = 200
  notFound = 404

Compiles to .js (types erased) + .d.ts (types preserved) — full IDE support via TypeScript Language Server. See docs/RIP-TYPES.md.

Standard Library

13 global helpers available in every Rip program — no imports needed:

p "hello"                       # console.log shorthand
pp {name: "Alice", age: 30}     # pretty-print JSON (also returns value)
warn "deprecated"               # console.warn
assert x > 0, "must be positive"
raise TypeError, "expected string"
todo "finish this later"
kind [1, 2, 3]                  # "array" (fixes typeof)
rand 10                         # 0-9
rand 5, 10                      # 5-10 inclusive
sleep! 1000                     # await sleep(1000)
exit 1                          # process.exit(1)
abort "fatal"                   # log to stderr + exit(1)
zip names, ages                 # [[n1,a1], [n2,a2], ...]
noop                            # () => {}

All use globalThis with ??= — override any by redeclaring locally.

Operators

OperatorExampleWhat it does
! (dammit)fetchData!Calls AND awaits
! (void)def process!Suppresses implicit return
!? (otherwise)val !? 5Default only if undefined (infix)
!? (defined)val!?True if not undefined (postfix)
?! (presence)@checked?!True if truthy, else undefined (Houdini operator)
? (existence)x?True if x != null
?: (ternary)x > 0 ? 'yes' : 'no'JS-style ternary expression
if...else (postfix)"yes" if cond else "no"Python-style ternary expression
?. ?.[] ?.()a?.b a?.[0] a?.()Optional chaining (ES6)
?[] ?()a?[0] a?(x)Optional chaining shorthand
?. =el?.scrollTop = 0Optional chain assignment — guarded write
= (render)= item.textContentExpression output as text node in render blocks
??a ?? bNullish coalescing
... (spread)[...items, last]Prefix spread (ES6)
//7 // 2Floor division
%%-1 %% 3True modulo
=~str =~ /Hello, (\w+)/Match (captures in _)
[//, n]str[/Hello, (\w+)/, 1]Extract capture n
.new()Dog.new()Ruby-style constructor
:: (prototype)String::trimString.prototype.trim
[-n] (negative index)arr[-1]Last element via .at()
* (string repeat)"-" * 40String repeat via .repeat()
< <= (chained)1 < x < 10Chained comparisons
|> (pipe)x |> fn or x |> fn(y)Pipe operator (first-arg insertion)
not inx not in arrNegated membership test
not ofk not of objNegated key existence
.= (method assign)x .= trim()x = x.trim() — compound method assignment
*> (merge assign)*>obj = {a: 1}Object.assign(obj, {a: 1})
or returnx = get() or return errGuard clause (Ruby-style)
?? throwx = get() ?? throw errNullish guard

Heredoc & Heregex

Heredoc — The closing ''' or """ position defines the left margin. All content is dedented relative to the column where the closing delimiter sits:

html = '''
    <div>
      <p>Hello</p>
    </div>
    '''
# Closing ''' at column 4 (same as content) — no leading whitespace
# Result: "<div>\n  <p>Hello</p>\n</div>"

html = '''
    <div>
      <p>Hello</p>
    </div>
  '''
# Closing ''' at column 2 — 2 spaces of leading whitespace preserved
# Result: "  <div>\n    <p>Hello</p>\n  </div>"

Raw heredoc — Append \ to the opening delimiter ('''\ or """\) to prevent escape processing. Backslash sequences like \n, \t, \u stay literal:

script = '''\
  echo "hello\nworld"
  sed 's/\t/  /g' file.txt
  \'''
# \n and \t stay as literal characters, not newline/tab

Heregex — Extended regex with comments and whitespace:

pattern = ///
  ^(\d{3})    # area code
  -(\d{4})    # number
///

vs React / Vue / Solid

ConceptReactVueSolidRip
StateuseState()ref()createSignal()x := 0
ComputeduseMemo()computed()createMemo()x ~= y * 2
EffectuseEffect()watch()createEffect()~> body

Rip's reactivity is framework-agnostic — use it with React, Vue, Svelte, or vanilla JS.

Rip UI

Load rip.min.js (~54KB Brotli) — the Rip compiler and UI framework in one file. Components are .rip source files, compiled on demand, rendered with fine-grained reactivity. No build step. No bundler.

<script defer src="rip.min.js" data-mount="Home"></script>

<script type="text/rip">
export Home = component
  @count := 0
  render
    div.counter
      h1 "Count: #{@count}"
      button @click: (-> @count++), "+"
      button @click: (-> @count--), "-"
</script>

That's it. All <script type="text/rip"> tags share scope — export makes names visible across tags. data-mount mounts the named component after all scripts execute. Two keywords (component and render) are all the language adds. Everything else (:= state, ~= computed, methods, lifecycle) is standard Rip.

Loading patterns:

<!-- Inline components + declarative mount -->
<script defer src="rip.min.js" data-mount="App"></script>
<script type="text/rip">export App = component ...</script>

<!-- Mount from code instead of data-mount -->
<script defer src="rip.min.js"></script>
<script type="text/rip">export App = component ...</script>
<script type="text/rip">App.mount '#app'</script>

<!-- External .rip files via data-src -->
<script defer src="rip.min.js" data-mount="App" data-src="
  components/header.rip
  components/footer.rip
  app.rip
"></script>

<!-- External .rip files via separate tags -->
<script defer src="rip.min.js" data-mount="App"></script>
<script type="text/rip" src="components/header.rip"></script>
<script type="text/rip" src="app.rip"></script>

<!-- Bundle — fetch all components from a server endpoint -->
<script defer src="/rip/rip.min.js" data-src="bundle" data-mount="App"></script>

<!-- Bundle with stash persistence (sessionStorage) -->
<script defer src="/rip/rip.min.js" data-src="bundle" data-mount="App" data-persist></script>

<!-- Mix bundles and individual files -->
<script defer src="/rip/rip.min.js" data-src="/rip/ui bundle header.rip" data-mount="App"></script>

Every component has a static mount(target) method — App.mount '#app' is shorthand for App.new().mount('#app'). Target defaults to 'body'.

The UI framework is built into rip-lang: file-based router, reactive stash, component store, and renderer. Try the demo — a complete app in one HTML file.

vs CoffeeScript

FeatureCoffeeScriptRip
OutputES5 (var, prototypes)ES2022 (classes, ?., ??)
ReactivityNoneBuilt-in
DependenciesMultipleZero
Self-hostingNoYes
Lexer3,558 LOC2,024 LOC
Compiler10,346 LOC3,293 LOC
Total17,760 LOC~11,890 LOC

Smaller codebase, modern output, built-in reactivity.

Browser

Run Rip directly in the browser — inline scripts and the console REPL both support await via the ! operator:

<script defer src="rip.min.js"></script>
<script type="text/rip">
  res = fetch! 'https://api.example.com/data'
  data = res.json!
  console.log data
</script>

The rip() function is available in the browser console:

rip("42 * 10 + 8")                                         // → 428
rip("(x * x for x in [1..5])")                             // → [1, 4, 9, 16, 25]
await rip("res = fetch! 'https://api.example.com/todos/1'; res.json!")  // → {id: 1, ...}

Try it live: shreeve.github.io/rip-lang

Architecture

Source  ->  Lexer  ->  emitTypes  ->  Parser  ->  S-Expressions  ->  Codegen  ->  JavaScript
           (1,778)    (types.js)     (359)       ["=", "x", 42]     (3,334)      + source map

Simple arrays (with .loc) instead of AST node classes. The compiler is self-hosting — bun run parser rebuilds from source.

ComponentFileLines
Lexer + Rewritersrc/lexer.js1,778
Grammarsrc/grammar/grammar.rip948
Parser Generatorsrc/grammar/solar.rip929
Parser (generated)src/parser.js359
Compiler + Codegensrc/compiler.js3,334
Component Systemsrc/components.js2,026
Browser Enginesrc/browser.js194
Source Mapssrc/sourcemaps.js189
Type Systemsrc/types.js1,091
Type Checkingsrc/typecheck.js442
REPLsrc/repl.js600
Total~11,890

The Rip Stack

Rip includes optional packages for full-stack development:

PackageVersionPurpose
rip-lang3.13.62Core language compiler
@rip-lang/server1.3.12Multi-worker app server (web framework, hot reload, HTTPS, mDNS)
@rip-lang/db1.3.15DuckDB server with official UI + ActiveRecord-style client
@rip-lang/uiUnified UI system — browser widgets, email components, shared helpers, Tailwind integration
@rip-lang/swarm1.2.18Parallel job runner with worker pool
@rip-lang/csv1.3.6CSV parser + writer
@rip-lang/schema0.3.8Unified schema → TypeScript types, SQL DDL, validation, ORM
VS Code Extension0.5.7Syntax highlighting, type intelligence, source maps
bun add -g @rip-lang/db    # Installs everything (rip-lang + server + db)

Implicit 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           # 1436 tests
bun run parser         # Rebuild parser
bun run build          # Build browser bundle

Release

# rip-lang + changed @rip-lang/* packages + @rip-lang/all
bun run bump

# Explicit version level
bun run bump patch
bun run bump minor
bun run bump major
  • bun run bump is the standard release flow for the npm ecosystem in this repo.
  • 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.
  • @rip-lang/all is released automatically as part of that flow; there is no separate manual release step for it.
  • packages/vscode is intentionally excluded and must be versioned and published separately.

Documentation

GuideDescription
docs/RIP-LANG.mdFull language reference (syntax, operators, reactivity, types, components)
docs/RIP-TYPES.mdType system specification
AGENTS.mdCompiler architecture, S-expressions, component system internals
AGENTS.mdAI agents — get up to speed for working on the compiler

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.

Keywords

rip

FAQs

Package last updated on 04 Apr 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts