npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@lykn/lang

v0.5.2

Published

[![][build-badge]][build] [![][crate-badge]][crate] [![][tag-badge]][tag] [![][docs-badge]][docs]

Downloads

217

Readme

Lykn

A modern Lisp for ECMAScript, the browser, and the Deno JavaScript runtime

Lykn is a Lisp designed for the modern web. Source is .lykn — typed functions, algebraic data types, pattern matching, immutable bindings, and a hygienic macro system. The lykn CLI is the language's surface: project scaffolding, build, test, format, and publish are all lykn <subcommand>. Compiled output (ECMAScript-conformant JavaScript) is an implementation detail; users work in Lykn at the source level.

Lykn has two syntax layers. Surface syntax is what you write — the language proper, with types, contracts, pattern matching, and macros. Kernel syntax is the IR the surface compiles into; it's a thin JavaScript dialect that exists for compiler maintainers and language internals, not for end-user code. This README focuses on surface; for the full surface reference, see assets/ai/SKILL.md.

For the design principles that govern Lykn's user experience, see docs/philosophy.md.

The name means good luck in Norwegian, luck in Swedish, and — if you squint at the Icelandic — closure.

Quick Start

# Install
brew install deno
cargo install lykn  # or: cargo build --release && cp target/release/lykn bin/

# Create a project
lykn new my-app
cd my-app

# Run it
lykn run packages/my-app/mod.lykn

# Run tests
lykn test

Quick taste

;; Immutable bindings
(bind greeting "hello, world")

;; Typed functions with runtime safety
(func greet
  :args (:string name)
  :returns :string
  :body (+ greeting ", " name "!"))

;; Threading macros
(bind result (-> 5 (+ 3) (* 2)))

;; Objects with keyword syntax
(bind user (obj :name "lykn" :version "0.5.0"))

;; Controlled mutation via cells
(bind counter (cell 0))
(swap! counter (=> (n) (+ n 1)))
(console:log (express counter))

;; Generator with typed yields
(genfunc range
  :args (:number start :number end)
  :yields :number
  :body
  (for (let i start) (< i end) (+= i 1)
    (yield i)))

;; Macros still work — define your own forms
(macro when (test (rest body))
  `(if ,test (block ,@body)))

(when (> result 0)
  (console:log "positive"))

Output:

1
positive

Architecture

lykn has two compiler implementations sharing the same syntax and semantics:

Rust compiler (standalone binary, no runtime dependencies):

.lykn source → reader → expander → classifier → analyzer → emitter → codegen → JavaScript

JS compiler (browser bundle + Deno):

.lykn source → reader → surface macros → expander → compiler → astring → JavaScript

Rust pipeline (crates/)

  • Reader (lykn-lang/reader) — S-expression parser with source locations
  • Expander (lykn-lang/expander) — macro expansion (user macros via Deno subprocess)
  • Classifier (lykn-lang/classifier) — S-expressions → typed surface AST
  • Analyzer (lykn-lang/analysis) — type registry, exhaustiveness checking, scope tracking, unused binding detection
  • Emitter (lykn-lang/emitter) — surface forms → kernel S-expressions
  • Codegen (lykn-lang/codegen) — kernel S-expressions → JavaScript text (pure Rust, no external dependencies)

JS pipeline (packages/lang/)

  • Reader (packages/lang/reader.js) — parses s-expressions, handles # dispatch (`, ,, ,@, #a(...), #o(...), #NNr, #;, #|...|#), dotted pairs
  • Surface macros (packages/lang/surface.js) — transforms high-level surface forms to kernel forms
  • Expander (packages/lang/expander.js) — three-pass macro expansion pipeline (Bawden's quasiquote algorithm)
  • Compiler (packages/lang/compiler.js) — kernel forms → ESTree AST → JS via astring
  • Browser shim (packages/browser/mod.js) — 73KB bundle with <script type="text/lykn"> support and window.lykn API

Toolchain

brew install deno         # runtime — needed to run compiled output
cargo install lykn        # the lykn CLI

User commands (working on a Lykn project)

lykn new my-app           # scaffold a new project
lykn run main.lykn        # run a Lykn program
lykn test                 # discover and run .lykn/.lyk tests
lykn test test/surface/   # test a specific directory
lykn fmt main.lykn        # format Lykn source
lykn check main.lykn      # syntax check
lykn build --dist         # stage all packages into dist/
lykn publish --jsr        # publish to JSR
lykn publish --npm        # publish to npm
lykn publish --jsr --dry-run   # verify without publishing

lykn lint for Lykn-source linting is planned for 0.6.0 — see docs/philosophy.md §0.6.0 commitments.

Contributor commands (working on Lykn itself)

These commands are for people working on the Lykn compiler and CLI, not for everyday Lykn users.

cargo build --release     # build the lykn compiler binary
cargo test                # Rust tests
cargo clippy              # lint Rust
cargo fmt                 # format Rust

Usage

Browser

<script src="dist/lykn-browser.js"></script>
<script type="text/lykn">
  ;; Macros work inline in the browser!
  (macro when (test (rest body))
    `(if ,test (block ,@body)))

  (bind el (document:query-selector "#output"))
  (when el
    (set! el:text-content "Hello from lykn!"))
</script>

Or use the API directly:

lykn.compile('(+ 1 2)')   // → "1 + 2;\n"
lykn.run('(+ 1 2)')       // → 3
await lykn.load('/app.lykn')

Note: import-macros is not available in the browser (no file system access). Inline macro definitions work.

CLI

# Create a new project
lykn new my-app

# Compile .lykn to JavaScript
lykn compile main.lykn                    # output to stdout
lykn compile main.lykn -o main.js         # output to file
lykn compile main.lykn --strip-assertions # omit type checks / contracts

# Run directly
lykn run main.lykn

# Format
lykn fmt main.lykn                        # stdout
lykn fmt -w main.lykn                     # in place

# Syntax check
lykn check main.lykn

# Build browser bundle
lykn build --browser

Build from source:

mkdir -p ./bin
cargo build --release && cp ./target/release/lykn ./bin

Run Examples

# Run a Lykn file directly
lykn run examples/surface/main.lykn

# Serve the browser examples — any static file server works
python3 -m http.server 5099
# Then open http://localhost:5099/examples/surface/browser.html

Both examples/surface/ (.lykn) and examples/kernel/ (.lyk) are available.

Supported forms

Surface forms

Surface syntax is the recommended way to write lykn. These forms expand to kernel forms at compile time.

Bindings & mutation

| lykn | JS | |---|---| | (bind x 1) | const x = 1; | | (bind :number x (compute)) | const x = compute(); with runtime type check | | (bind counter (cell 0)) | const counter = { value: 0 }; | | (swap! counter f) | counter.value = f(counter.value); | | (reset! counter 0) | counter.value = 0; | | (express counter) | counter.value | | (set! el:prop value) | el.prop = value; |

Functions

| lykn | JS | |---|---| | (func add :args (:number a :number b) :returns :number :body (+ a b)) | function add(a, b) { ... return a + b; } with type checks | | (func now (Date:now)) | function now() { return Date.now(); } | | (fn (:number x) (* x 2)) | x => { ...; x * 2; } with type check | | (func f :args ((object :string host :number port)) :body ...) | Destructured params with per-field types | | (genfunc range :args (:number a :number b) :yields :number :body ...) | Generator with typed yields | | (genfn () (yield 1) (yield 2)) | Anonymous generator |

Types & pattern matching

| lykn | JS | |---|---| | (type Option (Some :any value) None) | Constructor functions with { tag: "Some", value } | | (match opt ((Some v) v) (None fallback)) | Exhaustive if-chain on .tag | | (if-let ((Some user) (find id)) (greet user) "none") | Tag check + binding + branch | | (when-let ((Some user) (find id)) (greet user)) | Same without else branch |

Objects

| lykn | JS | |---|---| | (obj :name "x" :age 42) | { name: "x", age: 42 } | | (assoc user :age 43) | { ...user, age: 43 } | | (dissoc user :password) | Spread + delete | | (conj items new-item) | [...items, newItem] |

Threading macros

| lykn | JS | |---|---| | (-> x (+ 3) (* 2)) | (x + 3) * 2 | | (->> items (filter even?) (map double)) | map(filter(items, even), double) | | (-> user (get :name) (:to-upper-case)) | user["name"].toUpperCase() | | (some-> user (get :name) (:to-upper-case)) | IIFE with null checks + method call |

Equality & logic

| lykn | JS | |---|---| | (= a b) | a === b (strict equality, not assignment) | | (!= a b) | a !== b | | (= a b c) | a === b && b === c (variadic pairwise) | | (and x y) | x && y (short-circuit) | | (or x y) | x \|\| y (short-circuit) | | (not x) | !x |

Kernel forms

Kernel forms are the compilation targets for surface macros. You can use them directly for low-level control, JS interop, or when surface syntax doesn't cover a specific JS feature.

Basics

| lykn | JS | |---|---| | (const x 1) | const x = 1; | | (let x 1) | let x = 1; | | my-function | myFunction | | console:log | console.log | | this:-name | this.#_name | | (get arr 0) | arr[0] |

Functions

| lykn | JS | |---|---| | (=> (a b) (+ a b)) | (a, b) => a + b | | (function add (a b) (return (+ a b))) | function add(a, b) { return a + b; } | | (lambda (a) (return a)) | function(a) { return a; } | | (async (=> () (await (fetch url)))) | async () => await fetch(url) | | (=> ((default x 0)) x) | (x = 0) => x | | (function f (a (rest args)) ...) | function f(a, ...args) { ... } |

Modules

| lykn | JS | |---|---| | (import "mod" (a b)) | import {a, b} from "mod"; | | (import "mod" name) | import name from "mod"; | | (export (const x 42)) | export const x = 42; | | (export default my-fn) | export default myFn; | | (dynamic-import "./mod.js") | import("./mod.js") |

Control flow

| lykn | JS | |---|---| | (if cond a b) | if (cond) a; else b; | | (? test a b) | test ? a : b | | (for-of item items (f item)) | for (const item of items) { f(item); } | | (while cond body...) | while (cond) { body } | | (try body (catch e ...) (finally ...)) | try { body } catch(e) { ... } finally { ... } | | (switch x ("a" (f) (break)) (default (g))) | switch(x) { case "a": f(); break; default: g(); } | | (throw (new Error "oops")) | throw new Error("oops"); |

Expressions

| lykn | JS | |---|---| | (template "hi " name "!") | `hi ${name}!` | | (tag html (template ...)) | html`...` | | (object (name "x") age) | {name: "x", age} | | (array 1 2 (spread rest)) | [1, 2, ...rest] | | (regex "^hello" "gi") | /^hello/gi | | (new Thing a b) | new Thing(a, b) |

Destructuring

| lykn | JS | |---|---| | (const (object name age) person) | const {name, age} = person; | | (const (array first (rest tail)) list) | const [first, ...tail] = list; | | (const (object (alias data items)) obj) | const {data: items} = obj; | | (const (object (default x 0)) point) | const {x = 0} = point; | | (const (array _ _ third) arr) | const [, , third] = arr; |

Classes

Surface forms work inside class bodies — bind, = (equality), set!, threading macros all expand correctly.

| lykn | JS | |---|---| | (class Dog (Animal) ...) | class Dog extends Animal { ... } | | (assign this:name name) | this.name = name (class body only) | | (field -count 0) | #_count = 0; | | (get area () (return x)) | get area() { return x; } | | (static (field count 0)) | static count = 0; | | (async (fetch-data () ...)) | async fetchData() { ... } |

Operators

| lykn | JS | Notes | |---|---|---| | (+ a b c) | a + b + c | Arithmetic | | (++ x) | ++x | Prefix increment | | (+= x 1) | x += 1 | Compound assignment | | (** base exp) | base ** exp | Exponentiation | | (?? a b) | a ?? b | Nullish coalescing | | (=== a b) | a === b | Kernel strict equality | | (= x 1) | x = 1 | Kernel assignment (surface = is equality) |

Macros

| lykn | What it does | |---|---| | (macro when (test (rest body)) `(if ,test (block ,@body))) | Define a macro with quasiquote template | | (import-macros "./lib.lykn" (when unless)) | Import macros from a local file | | (import-macros "jsr:@lykn/testing" (test is-equal)) | Import macros from a published package | | `(if ,test ,@body) | Quasiquote with unquote and splicing | | temp#gen | Auto-gensym (hygienic binding) | | (gensym "prefix") | Programmatic gensym |

Data literals and sugar

| lykn | JS | |---|---| | #a(1 2 3) | [1, 2, 3] | | #o((name "x") (age 42)) | {name: "x", age: 42} | | #16rff | 255 (radix literal) | | #2r11110000 | 240 (binary) | | (cons 1 2) | [1, 2] | | (list 1 2 3) | [1, [2, [3, null]]] | | (car x) / (cdr x) | x[0] / x[1] | | #; expr | Expression comment (discards next form) | | #\| ... \|# | Nestable block comment |

Testing

lykn includes a testing DSL via the @lykn/testing macro module:

(import-macros "jsr:@lykn/testing" (test suite is-equal ok))

(suite "arithmetic"
  (test "addition"
    (is-equal (+ 1 1) 2))
  (test "positive"
    (ok (> 5 0))))

Compiles to Deno.test() + @std/assert — no runtime dependency or custom test runner. Available assertions: is, is-equal, ok, is-thrown, matches.

Test files use *_test.lykn / *.test.lykn (or .lyk) naming. Run with lykn test.

Design principles

These are summarized here; see docs/philosophy.md for the full doc with rationale, implications, and audit checklist.

  • Source-only tree. A Lykn project's source tree contains only .lykn/.lyk files (plus configuration). Compiled output lives in a build-artifact directory, gitignored. The pattern matches Rust, Go, and other cleanly-compiled languages.
  • Lykn-only tooling. Every project operation is lykn <command>. The lykn CLI is the language's surface; underlying tools do their work invisibly. Users don't reach around the CLI.
  • Compiler-owned output quality. The compiler is responsible for producing JavaScript that's properly formatted, lint-clean, and behaviorally correct. Errors in compiled JS are compiler bugs, not user-facing issues.
  • No runtime. Compiled Lykn is just JavaScript — no runtime library shipped to the browser, nothing extra in published packages.
  • Self-contained. The Rust compiler is a single binary with no runtime dependencies. The browser bundle is 73KB and inspectable.
  • Two implementations. Rust for the compiler and dev-side tooling (fast, single binary). JS for the browser bundle and in-browser <script> workflow.

References

  • EBNF grammar — formal grammar specification for lykn syntax
  • ESTree spec — the AST format lykn targets
  • astring — ESTree to JS code generation
  • Bawden 1999 — "Quasiquotation in Lisp", the algorithm behind lykn's macro expansion
  • Fennel — inspiration for enforced gensym hygiene model
  • eslisp — spiritual ancestor; reference implementation
  • BiwaScheme — inspiration for the in-browser <script> workflow

License

Apache-2.0