Skip to content

Instantly share code, notes, and snippets.

@jaawerth
Last active April 11, 2024 02:49
Show Gist options
  • Save jaawerth/7501b0ae26ce82dbad76a51ecb8cdb78 to your computer and use it in GitHub Desktop.
Save jaawerth/7501b0ae26ce82dbad76a51ecb8cdb78 to your computer and use it in GitHub Desktop.
Yep, now we're compiling to cursed fennel, THEN evaluating to lua. You don't even want to see the Lua, really.

zalgo.fnl

  1. Like many Lisps, Fennel is extremely permissive about what it considers to be valid identifiers in code so long as delimiters like ()[]{} are properly respected and known identifiers/symbols (builtin macros/special forms, known functions, etc) are left alone.
  2. And by anything, I do mean anything.
  3. Zalgo-text generators have been pretty popular lately; they more or less just dump patterns of garbage unicode codepoints between the actual characters in strings until computers can no longer properly render it. It's pretty funny; sometimes it will look different in different contexts even in the same application on the same machine.
  4. So why the hell not, let's have Fennel compile itself to zalgo.

zalgo fnl

What?

In the first 80 or so lines of zalgo.fnl, you'll see a quick and dirty implementation of one of these cursed text generators. Then, starting on line 82, we wrap the whole thing in a Fennel macro. All it's really doing is walking the syntax tree so that every symbol that doesn't already exist in the compiler's scope (because we want to be able to invoke pre-existing functions and macros), accessing the symbol's underlying string, and promptly injecting it with trash unicode beffore letting the compiler take it from there.

That's more or less it! You can see what the processed Fennel looks like from the macrodebug call in teste-zalgo.fnl below. That's the code it will ultimately turn into lua and evaluate to get the printed output in the screenshot above.

Fine, what about compiling to Lua?

That would be this horrible mess:

zalgo-fnl-sample-compiled-lua

The test-zalgo.fnl file below totals somewhere around 800 bytes; the actual code we're corrupting weighs in at less than half of that, while the lua generated by (curse ...) on just those 400-or-so bytes brings it to about 65kb of mostly escaped unicode bytes like you see above. Yes, this is absolutely pointless, but if you're still reading this it means that, like me, you also enjoy shitposting-as-code. Welcome!

(import-macros {: curse} :zalgo)
(macrodebug
(curse
(fn range [s e dx] (fn i-next [[e dx] i]
(match (+ i dx)
(where v (<= v e)) v))
(values i-next [e dx] (- s dx)))
(fn greetings-from-fnl []
(icollect [i (range 1 10 1)]
"You may enter text into the REPL, but you may neverl leave 💖" ))
(print (table.concat (greetings-from-fnl)
"\n"))))
; (print "\n----\nOutput:\n")
; (curse
; (fn range [s e dx]
; (values (fn i-next [[e dx] i]
; (let [i (+ i dx)]
; (when (<= i e) i)))
; [e dx] (- s dx)))
; (fn greetings-from-fnl []
; (icollect [i (range 1 10 1)]
; "You may enter text into the REPL, but you may neverl leave 💖" ))
; (print (table.concat (greetings-from-fnl)
; "\n")))
;; fennel-ls: macro-file
; Characters taken from http://str.blogsite.org/Zalgo.htm.
(local mods
{:down [0x0316 0x0317 0x0318 0x0319 0x031C
0x031D 0x031E 0x031F 0x0320 0x0324
0x0325 0x0326 0x0329 0x032A 0x032B
0x032C 0x032D 0x032E 0x032F 0x0330
0x0331 0x0332 0x0333 0x0339 0x033A
0x033B 0x033C 0x0345 0x0347 0x0348
0x0349 0x034D 0x034E 0x0353 0x0354
0x0355 0x0356 0x0359 0x035A 0x0323]
:mid [0x0315 0x031B 0x0340 0x0341 0x0358
0x0321 0x0322 0x0327 0x0328 0x0334
0x0335 0x0336 0x034F 0x035C 0x035D
0x035E 0x035F 0x0360 0x0362 0x0338
0x0337 0x0361 0x0489]
:up [0x030D 0x030E 0x0304 0x0305 0x033F
0x0311 0x0306 0x0310 0x0352 0x0357
0x0351 0x0307 0x0308 0x030A 0x0342
0x0343 0x0344 0x034A 0x034B 0x034C
0x0303 0x0302 0x030C 0x0350 0x0300
0x0301 0x030B 0x030F 0x0312 0x0313
0x0314 0x033D 0x0309 0x0363 0x0364
0x0365 0x0366 0x0367 0x0368 0x0369
0x036A 0x036B 0x036C 0x036D 0x036E
0x036F 0x033E 0x035B 0x0346 0x031A]})
(fn env [key] (let [getenv (-> (. _G :os) (?. :getenv) (or #nil))]
(getenv key)))
(local *intensity-factor* (match (tonumber (env :INTENSITY_FACTOR))
n n
_ 1))
(let [hash-num #(-> (tostring []) (string.sub 10) (tonumber 16))
n1 (hash-num) n2 (if _G.os (_G.os.time)
(math.floor (/ (hash-num) 2)))]
(math.randomseed n1 n2))
(local intensities {:down 5 :up 5 :mid 5})
(fn rand-next [[lower upper] ?n]
(values (+ (or ?n 0) 1)
(math.random lower upper)))
(fn rand-sample [n src-tbl]
(local (N seen out) (values (math.min n (# src-tbl)) {} []))
(icollect [idx (values #(math.random 1 (# src-tbl)))
:into out :until (= N (# out))]
(let [v (. src-tbl idx)]
(when (not (. seen v))
(tset seen v true)
v))))
(fn pick-chars [charset len unique? into]
(let [charset (rand-sample len charset)
bounds [1 (# charset)]]
(icollect [n idx (values rand-next bounds 0) :until (= n len)
:into into]
(utf8.char (. charset idx)))))
(fn curse-string [s ?intensities skip?]
; (local intensify #((math.random 1 10)))
(let [skip? (or skip? #false)
intensities (or ?intensities intensities)
out (table.concat
(accumulate [tgt [] _ b (ipairs [(s:byte 1 (s:len))])]
(let [ch (string.char b)
skip (skip? ch)]
(if skip
(set-forcibly! ch skip)
(each [cskey x (pairs intensities)]
(pick-chars (. mods cskey)
(* (or *intensity-factor* 1)
(math.random 3 x)) nil tgt)))
(table.insert tgt ch)
tgt))
"")]
out))
(fn purge [s]
(let [reverser {}
_ (each [_ evil-bytes (pairs mods)]
(collect [_ b (ipairs evil-bytes) :into reverser]
(values b true)))]
(-> (icollect [_offset c (utf8.codes s)]
(when (not (. reverser c)) (utf8.char c)))
(table.concat ""))))
(local cache {})
(fn curse-expr [expr]
(let [scope (get-scope)
curse-cached #(let [s (tostring $)
cursed (or (and (. cache s)) (curse-string s))]
(doto cache (tset $ cursed)
(tset s cursed))
cursed)
curse-seq #(icollect [_ v (ipairs $)]
(if (= :string (type v))
(curse-cached v)
(curse-expr v)))]
(if (list? expr)
(list (unpack (curse-seq expr)))
(sequence? expr)
(sequence (unpack (curse-seq expr)))
(= :string (type expr)) (curse-string expr)
(and (sym? expr) (not (multi-sym? expr))
(let [s (tostring expr)]
(not (or (= :nil s) (= :false s) (= :true s)
(. scope.macros s) (. scope.specials s)
(. _G s) (= :where s)))))
(sym (curse-cached expr))
expr)))
{:curse (fn curse-do [...]
`(do ,(unpack (curse-expr (sequence ...)))))
: curse-expr
:intensities (fn [?up ?mid ?down]
"Get and set the params for the up/down/mid intensities,
which controls how aggressively to inject unicode."
(if (= nil ?up ?mid ?down)
;; getter
`(values ,(unpack (icollect [_ k (ipairs [:up :mid :down])]
(* *intensity-factor* (. intensities k)))))
;; setter
(collect [_ [k v] (ipairs [[:up ?up] [:mid ?mid] [:down ?down]]) :into intensities]
(when v (values k v)))))}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment