lollang
v0.1.0
Published
A laughter-keyword language that transpiles to JavaScript. Every keyword is a laugh.
Maintainers
Readme
lollang
A laughter-keyword language that transpiles to JavaScript. Every keyword is a laugh.
haha factorial(n) {
lmfao (n <= 1) { rofl 1; }
rofl n * factorial(n - 1);
}
xd(factorial(6)); // 720What is it
LOLLang is a tiny language whose every keyword is a variant of laughter
(haha, lol, rofl, kek, lmao, …). It transpiles to JavaScript via a
direct token-substitution and is otherwise a strict subset of JS — the
operators, identifier rules, and expression grammar are all unchanged.
Two things make this package interesting:
unlulzcate— translates.lolsource to JavaScript. A single regex pass that preserves strings, template literals, and comments verbatim.oblulzcate— the inverse: JavaScript → LOL. AST-aware (via Acorn) so thatelse if,console.log(…), andundefinedreferences are handled correctly — things a naïve regex cannot do.
The full language spec is at docs/SPEC.md.
Install
npm install lollang
# or globally for the CLI
npm install -g lollangRequires Node ≥ 18.
CLI
lollang unlulzcate hello.lol -o hello.js # LOL → JS
lollang oblulzcate hello.js -o hello.lol # JS → LOL
lollang run hello.lol # transpile + execute
lollang repl # interactive lol> promptStdin and stdout are first-class: pipe in, pipe out.
echo 'xd("hi");' | lollang unlulzcate
# → console.log("hi");
cat src/index.js | lollang oblulzcate > src/index.lolAliases: transpile ≡ unlulzcate, obfuscate ≡ oblulzcate.
API
import { unlulzcate, oblulzcate, run, startRepl } from "lollang";
unlulzcate('xd("hi");'); // 'console.log("hi");'
oblulzcate('console.log("hi");'); // 'xd("hi");'
await run("lol x = 41; x + 1;"); // 42
await startRepl(); // interactiveoblulzcate accepts options for the asymmetric edges:
oblulzcate(source, {
translateConsoleLog: false, // keep console.log literal
translateUndefined: false, // keep undefined literal
});Keyword map
| LOL | JS | | LOL | JS |
| ----------- | ------------- | --- | ---------- | ---------- |
| lol | let | | lulz | break |
| lmao | const | | jaja | continue |
| haha | function | | bahaha | true |
| rofl | return | | mwahaha | false |
| lmfao | if | | teehee | null |
| hehe | else if | | imded | undefined|
| kek | else | | ahaha | class |
| hihi | while | | omegalul | new |
| heh | for | | me | this |
| lolwut | try | | kekw | switch |
| lolnope | catch | | pepega | case |
| ded | throw | | lulw | default |
| xd | console.log | | yoink | import |
| yeet | export | | giggle | async |
| waitforit | await | | | |
Identifiers that collide with keywords can be escaped with a $ prefix:
$haha becomes the JS identifier haha.
How it works
unlulzcate (LOL → JS)
A single regex pass. The trick is the alternation order: string literals,
template literals, line comments, and block comments are matched first,
then the engine falls through to identifier-shaped tokens, then any
remaining single character. Only identifier tokens are looked up in the
keyword map, so lol x = "haha"; cannot accidentally rewrite the word
inside the string.
"…" | '…' | `…` | // … | /* … */ | $IDENT | \bIDENT\b | .
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^ ^^^^^^^^^ ^
preserved verbatim escape candidate fallbackoblulzcate (JS → LOL)
The inverse needs an AST because three rewrites are not local:
| Rewrite | Why an AST |
| ----------------------------------- | ---------------------------------------------------- |
| else if → hehe | Two JS tokens with arbitrary whitespace between. |
| console.log(…) → xd(…) | Must rewrite the callee of a call, not every match.|
| undefined → imded | Only value-position references; never property keys, member access, or binding names. |
Implementation: Acorn tokenizes the source for keyword-position edits, the parser produces an AST for the three special cases, and edits are applied right-to-left so offsets stay valid.
Limitations
- Reserved identifiers. You can't name a binding after a laugh keyword
in LOL source unless you escape it with
$(e.g.$haha). console.logonly. The reverse direction rewrites only the call form ofconsole.log. Standalone references (const f = console.log;) stay literal — by design, because rewriting them asxdwould change behavior ifconsole.logwere later reassigned.- No source maps. v1 emits no source maps; line numbers may shift due
to length-changing substitutions like
xd↔console.log. - No type system. Add
:typeannotations and strip them yourself if you want TypeScript-shaped hints.
Development
npm install
npm run build # tsup → dist/ (esm + cjs + .d.ts)
npm test # vitest
npm run check # lint + typecheck + test
npm run dev # tsup --watchTests cover every spec example plus round-trip JS → LOL → JS for a corpus of common shapes (classes, async, modules, switch, control flow).
License
MIT © 2026 aj
