~technomancy/fennel-lang.org

a8d9682682fe164a537651896a0ba3ad1ade0026 — Phil Hagelberg 1 year, 9 months ago f5aaf95
Ports see.lua to see.fnl so we can use --require-as-include

Having this all in one file is pretty critical; otherwise we are stuck
with runtime require which makes an HTTP request in the main thread
which destroys responsiveness in the browser and is Very Bad.
8 files changed, 302 insertions(+), 419 deletions(-)

M .gitignore
M .gitmodules
M Makefile
M README.md
A fennel-highlight
A see.fnl
D see.lua
D signups.fnl
M .gitignore => .gitignore +2 -0
@@ 28,3 28,5 @@
/macros.html
/coc.html
/values.html
/syntax-table.fnl
/see.lua

M .gitmodules => .gitmodules +3 -0
@@ 1,3 1,6 @@
[submodule "fennel"]
	path = fennel
	url = https://git.sr.ht/~technomancy/fennel
[submodule "fennel-highlight"]
	path = fennel-highlight
	url = https://github.com/firoxer/fennel-highlight/

M Makefile => Makefile +9 -1
@@ 34,7 34,7 @@ fennelview.lua: fennel/fennel fennel/src/fennel/view.fnl
# would be better to use a submodule for this too, but it changes much less
# antifennel.lua: ../antifennel/antifennel ; cp $< $@

fennel-syntax.xml: syntax.fnl fennel/fennel
fennel-syntax.xml: syntax.fnl fennel/fennel.lua
	fennel/fennel $< > $@

coc.html: fennel/CODE-OF-CONDUCT.md; $(PANDOC) -o $@ $<


@@ 82,6 82,14 @@ tagdocs: tags $(TAGDOCS)
lua: $(LUA)
clean: cleantagdirs ; rm -f $(HTML) index.html $(LUA)

see.lua: see.fnl fennel-highlight/highlight.fnl syntax-table.fnl
	fennel/fennel --add-fennel-path "fennel-highlight/?.fnl" \
		--compile --require-as-include --skip-include fennel,js $< > $@

syntax-table.fnl: fennel/fennel
	fennel/fennel --eval \
		"(let [{: syntax : view} (require :fennel)] (print (view (syntax))))" > $@

upload: $(HTML) $(LUA) $(TAGDIRS) index.html init.lua repl.fnl fennel.css \
		fengari-web.js repl-worker.js repl-worker.lua .htaccess 404.html fennel \
		see.html see.lua antifennel.lua see-worker.lua see-worker.js logo.svg \

M README.md => README.md +14 -0
@@ 16,11 16,25 @@ The `conf/` subdirectory contains the code for various years of the
The `survey/` subdirectory contains the code for the Fennel survey and
is not currently deployed automatically via CI like the rest of the site.

The `wiki/` subdirectory contains code for proxying the content from
the old github wiki onto the wiki.fennel-lang.org subdomain.

## Deployment

The HTML for the site is
[built and uploaded in the CI](https://builds.sr.ht/~technomancy/fennel-lang.org).

## Credits

This site is built using the following tools:

* [Fennel](https://fennel-lang.org)
* [antifennel](https://git.sr.ht/~technomancy/antifennel)
* [Fengari](https://fengari.io/)
* [fennel-highlight](https://github.com/firoxer/fennel-highlight)
* [GNU Make](https://www.gnu.org/software/make/)
* [pandoc](https://pandoc.org)

Code and documentation copyright © 2018-2023 Phil Hagelberg and contributors

Released under the MIT/X11 license; same as Fennel; see LICENSE

A fennel-highlight => fennel-highlight +1 -0
@@ 0,0 1,1 @@
Subproject commit 40579dab8a56a66b38ce7b8427f9aad7522a5805

A see.fnl => see.fnl +273 -0
@@ 0,0 1,273 @@
(set package.path :./?.lua)
(local js (require :js))

;; minimal shims just to allow the compilers to load in Fengari
(let [noop #nil]
  (set package.loaded.ffi {:typeof noop})
  (set _G.os {:getenv noop})
  (set _G.io {:open noop})
  (set _G.bit {:band #(band $1 $2) :rshift #(rshift $1 $2)})
  (set _G.unpack table.unpack))

(fn _G.print [...] (js.global.console:log ...))

(local document js.global.document)
(local compile-fennel (document:getElementById :compile-fennel))
(local compile-lua (document:getElementById :compile-lua))
(local out (document:getElementById :out))
(local fennel-source (document:getElementById :fennel-source))
(local fennel-highlighted (document:getElementById :fennel-highlighted))
(local lua-source (document:getElementById :lua-source))
(local lua-highlighted (document:getElementById :lua-highlighted))

(fn status [msg success]
  (set out.innerHTML msg)
  (set out.style.color (or (and success :black) "#dd1111")))

;; ctrl-enter to compile
(fn fennel-source.onkeydown [_ e]
  (when (not e) (set-forcibly! e js.global.event))
  (when (and (= (or e.key e.which) :Enter)
             e.ctrlKey)
    (compile-fennel.onclick)
    false))

(fn lua-source.onkeydown [_ e]
  (when (not e) (set-forcibly! e js.global.event))
  (when (and (= (or e.key e.which) :Enter) e.ctrlKey)
    (compile-lua.onclick)
    false))

;; Syntax highlighting is adapted from this guide:
;; https://css-tricks.com/creating-an-editable-textarea-that-supports-syntax-highlighted-code/
;;
;; TL;DR: To highlight syntax in a user-editable textarea, we
;; 1. Copy the contents of the textarea on every edit
;; 2. Run the contents through a highlighting function to get styled HTML
;; 3. Set the HTML into a pre element that's just behind the textarea
;; 4. Make the textarea completely transparent except for the input caret
;;    and ensure the input textarea and output pre have the same dimensions
;;    using a bunch of CSS

(fn highlight-fennel []
  (let [highlight (require :highlight)
        syntax-table (require :syntax-table)]
    (set fennel-highlighted.innerHTML
         (highlight.for-html syntax-table fennel-source.value))))

(fn highlight-lua []
  (set lua-highlighted.innerHTML lua-source.value))

;; initial run, since there could be something left in the textarea
;; after a page refresh
(highlight-fennel)
(highlight-lua)

(fn fennel-source.onscroll []
  (set fennel-highlighted.scrollTop fennel-source.scrollTop)
  (set fennel-highlighted.scrollLeft fennel-source.scrollLeft))

(fn lua-source.onscroll []
  (set lua-highlighted.scrollTop lua-source.scrollTop)
  (set lua-highlighted.scrollLeft lua-source.scrollLeft))

(fn fennel-source.oninput [] (highlight-fennel) (fennel-source.onscroll))
(fn lua-source.oninput [] (highlight-lua) (lua-source.onscroll))

(fn update-fennel-source [new-value]
  (set fennel-source.value new-value)
  (highlight-fennel))

(fn update-lua-source [new-value]
  (set lua-source.value new-value)
  (highlight-lua))

(local anti-msg
       (.. "Compiled Lua to Fennel.\n\n"
           "Note that compiling Lua to Fennel can result in some"
           " strange-looking code when\nusing constructs that Fennel"
           " does not support natively, like early returns."))

(fn init-worker [auto-click]
  (let [worker (js.new js.global.Worker :/see-worker.js)]
    (fn send [fennel? code]
      ;; we can't send tables to workers, so we have to encode everything in
      ;; strings. use an initial space for Fennel and initial tab for Lua code.
      (let [prefix (or (and fennel? " ") "\t")]
        (worker:postMessage (.. prefix code))))

    (fn worker.onmessage [_ e]
      (set out.innerHTML e.data) ;; loaded message
      ;; don't set handlers until we've loaded
      (fn compile-fennel.onclick [] (send true fennel-source.value))
      (fn compile-lua.onclick [] (send false lua-source.value))
      (fn worker.onmessage [_ event]
         ;; because we can't send tables as events, we encode the type of the
         ;; message in the last character of the string.
        (if (event.data:match " $")
            (do
              (update-lua-source event.data)
              (status "Compiled Fennel to Lua." true))
            (event.data:match "\t$")
            (do
              (update-fennel-source event.data)
              (status anti-msg true))
            (status event.data false)))
      (when (and auto-click auto-click.onclick)
        (auto-click.onclick)))))

(fn load-direct [auto-click]
  (let [antifennel (dofile :antifennel.lua)
        fennel (require :fennel)]
    (fn compile-fennel.onclick []
      (let [(ok code) (pcall fennel.compileString fennel-source.value)]
        (if ok
            (do
              (update-lua-source code)
              (status "Compiled Fennel to Lua." true))
            (status (tostring code) false))))
    (fn compile-lua.onclick []
      ;; for Lua that doesn't parse, antifennel gives crummy error messages
      (match (load lua-source.value)
        (nil msg) (status (.. "Lua: " msg) false)
        _ (match (pcall antifennel lua-source.value)
            (true code) (do (update-fennel-source code)
                            (status anti-msg true))
            (_ msg) (status (tostring msg) false))))
    (set out.innerHTML (.. "Loaded Fennel " fennel.version " in " _VERSION))
    (when (and auto-click auto-click.onclick)
      (auto-click.onclick))))

(var started false)

(fn init [auto-click]
  (when (not started)
    (set started true)
    (set out.innerHTML "Loading...")
    (if js.global.Worker (init-worker auto-click)
        js.global.setTimeout (js.global:setTimeout #(load-direct auto-click))
        ;; if we have no way at all to defer; just load in the foreground
        (load-direct auto-click))))

(set compile-fennel.onclick init)
(set compile-lua.onclick init)
(set fennel-source.onfocus init)
(set lua-source.onfocus init)

(local fennel-samples {"fibonacci" "(fn fibonacci [n]
 (if (< n 2)
  n
  (+ (fibonacci (- n 1)) (fibonacci (- n 2)))))

(print (fibonacci 10))"
                       "pong movement"
                       ";; Read the keyboard, move player accordingly
(local dirs {:up [0 -1] :down [0 1]
            :left [-1 0] :right [1 0]})

(each [key delta (pairs dirs)]
  (when (love.keyboard.isDown key)
    (let [[dx dy] delta
          [px py] player
          x (+ px (* dx player.speed dt))
          y (+ py (* dy player.speed dt))]
      (world:move player x y))))"
                       "walk" "(fn walk-tree [root f custom-iterator]
  (fn walk [iterfn parent idx node]
    (when (f idx node parent)
      (each [k v (iterfn node)]
        (walk iterfn node k v))))
  (walk (or custom-iterator pairs) nil nil root)
  root)"})

(local lua-samples {"antifennel" "local function uncamelize(name)
   local function splicedash(pre, cap)
     return pre .. \"-\" .. cap:lower()
   end
   return name:gsub(\"([a-z0-9])([A-Z])\", splicedash)
end

local function mangle(name, field)
   if not field and reservedFennel[name] then
     name = \"___\" .. name .. \"___\"
   end
   return field and name or
      uncamelize(name):gsub(\"([a-z0-9])_\", \"%1-\")
end

local function compile(rdr, filename)
   local ls = lex_setup(rdr, filename)
   local ast_builder = lua_ast.New(mangle)
   local ast_tree = parse(ast_builder, ls)
   return letter(compiler(nil, ast_tree))
end"
                    "love.run" "function love.run()
   love.load()
   while true do
      love.event.pump()
      local needs_refresh = false
      for name, a,b,c,d,e,f in love.event.poll() do
         if(type(love[name]) == \"function\") then
            love[name](a,b,c,d,e,f)
            needs_refresh = true
         elseif(name == \"quit\") then
            os.exit()
         end
      end
      for _,c in pairs(internal.coroutines) do
         local ok, val = coroutine.resume(c)
         if(ok and val) then needs_refresh = true
         elseif(not ok) then print(val) end
      end
      for i,c in lume.ripairs(internal.coroutines) do
         if(coroutine.status(c) == \"dead\") then
            table.remove(internal.coroutines, i)
         end
      end
      if(needs_refresh) then refresh() end
      love.timer.sleep(0.05)
   end
end"
                    "sample select"
                    "local sample_lua = document:getElementById(\"samples\")
local lua_samples = {}

for name, sample in pairs(lua_samples) do
   local option = document:createElement(\"option\")
   option.innerHTML = name
   sample_lua:appendChild(option)
end

function sample_lua.onchange(self, e)
   init()
   local code = lua_samples[self.value]
   if code then lua_source.value = code end
end"})

(fn init-samples [id samples update-source]
  (let [select (document:getElementById id)]
    (each [name sample (pairs samples)]
      (let [option (document:createElement :option)]
        (set option.innerHTML name)
        (select:appendChild option)))
    (fn select.onchange [self e]
      (init)
      (match (. samples self.value)
        code (update-source code)))))

(init-samples :sample-fennel fennel-samples update-fennel-source)
(init-samples :sample-lua lua-samples update-lua-source)

(when js.global.URLSearchParams
  (let [params (js.new js.global.URLSearchParams document.location.search)
        fennel-param (params:get :fennel)
        lua-param (params:get :lua)]
    (if (not= (tostring fennel-param) :null)
        (do
          (update-fennel-source fennel-param)
          (init compile-fennel))
        (not= (tostring lua-param) :null)
        (do
          (update-lua-source lua-param)
          (init compile-lua)))))


D see.lua => see.lua +0 -407
@@ 1,407 0,0 @@
package.path = "./?.lua"
local js = require("js")

-- minimal shims just to allow the compilers to load in Fengari
package.loaded.ffi = {typeof=function() end}
os = {getenv=function() end}
io = {open=function() end}
bit = {band = function(a,b) return a & b end,
       rshift=function(a,b) return a >> b end}
unpack = table.unpack

function print(...) js.global.console:log(...) end

local document = js.global.document
local compile_fennel = document:getElementById("compile-fennel")
local compile_lua = document:getElementById("compile-lua")
local out = document:getElementById("out")

local fennel_source = document:getElementById("fennel-source")
local fennel_highlighted = document:getElementById("fennel-highlighted")

local lua_source = document:getElementById("lua-source")
local lua_highlighted = document:getElementById("lua-highlighted")

local status = function(msg, success)
   out.innerHTML = msg
   out.style.color = success and "black" or "#dd1111"
end

-- Ctrl-enter to compile
fennel_source.onkeydown = function(_, e)
   if not e then e = js.global.event end
   if (e.key or e.which) == "Enter" and e.ctrlKey then
      compile_fennel.onclick()
      return false
   end
end

lua_source.onkeydown = function(_,e)
   if not e then e = js.global.event end
   if (e.key or e.which) == "Enter" and e.ctrlKey then
      compile_lua.onclick()
      return false
   end
end

-- Syntax highlighting is adapted from this guide:
-- https://css-tricks.com/creating-an-editable-textarea-that-supports-syntax-highlighted-code/
--
-- TL;DR: To highlight syntax in a user-editable textarea, we
-- 1. Copy the contents of the textarea on every edit
-- 2. Run the contents through a highlighting function to get styled HTML
-- 3. Set the HTML into a pre element that's just behind the textarea
-- 4. Make the textarea completely transparent except for the input caret
--    and ensure the input textarea and output pre have the same dimensions
--    using a bunch of CSS

local syntax = {["#"] = {["special?"] = true}, ["%"] = {["special?"] = true}, ["*"] = {["special?"] = true}, ["+"] = {["special?"] = true}, ["-"] = {["special?"] = true}, ["->"] = {["macro?"] = true}, ["->>"] = {["macro?"] = true}, ["-?>"] = {["macro?"] = true}, ["-?>>"] = {["macro?"] = true}, ["."] = {["special?"] = true}, [".."] = {["special?"] = true}, ["/"] = {["special?"] = true}, ["//"] = {["special?"] = true}, [":"] = {["special?"] = true}, ["<"] = {["special?"] = true}, ["<="] = {["special?"] = true}, ["="] = {["special?"] = true}, [">"] = {["special?"] = true}, [">="] = {["special?"] = true}, ["?."] = {["macro?"] = true}, ["^"] = {["special?"] = true}, _G = {["global?"] = true}, __ = {["global?"] = true}, ___replLocals___ = {["global?"] = true}, accumulate = {["binding-form?"] = true, ["body-form?"] = true, ["macro?"] = true}, ["and"] = {["special?"] = true}, arg = {["global?"] = true}, assert = {["function?"] = true, ["global?"] = true}, band = {["special?"] = true}, bit32 = {["global?"] = true}, ["bit32.arshift"] = {["function?"] = true, ["global?"] = true}, ["bit32.band"] = {["function?"] = true, ["global?"] = true}, ["bit32.bnot"] = {["function?"] = true, ["global?"] = true}, ["bit32.bor"] = {["function?"] = true, ["global?"] = true}, ["bit32.btest"] = {["function?"] = true, ["global?"] = true}, ["bit32.bxor"] = {["function?"] = true, ["global?"] = true}, ["bit32.extract"] = {["function?"] = true, ["global?"] = true}, ["bit32.lrotate"] = {["function?"] = true, ["global?"] = true}, ["bit32.lshift"] = {["function?"] = true, ["global?"] = true}, ["bit32.replace"] = {["function?"] = true, ["global?"] = true}, ["bit32.rrotate"] = {["function?"] = true, ["global?"] = true}, ["bit32.rshift"] = {["function?"] = true, ["global?"] = true}, bnot = {["special?"] = true}, bor = {["special?"] = true}, bxor = {["special?"] = true}, case = {["body-form?"] = true, ["macro?"] = true}, ["case-try"] = {["body-form?"] = true, ["macro?"] = true}, collect = {["binding-form?"] = true, ["body-form?"] = true, ["macro?"] = true}, collectgarbage = {["function?"] = true, ["global?"] = true}, comment = {["body-form?"] = true, ["special?"] = true}, coroutine = {["global?"] = true}, ["coroutine.create"] = {["function?"] = true, ["global?"] = true}, ["coroutine.isyieldable"] = {["function?"] = true, ["global?"] = true}, ["coroutine.resume"] = {["function?"] = true, ["global?"] = true}, ["coroutine.running"] = {["function?"] = true, ["global?"] = true}, ["coroutine.status"] = {["function?"] = true, ["global?"] = true}, ["coroutine.wrap"] = {["function?"] = true, ["global?"] = true}, ["coroutine.yield"] = {["function?"] = true, ["global?"] = true}, debug = {["global?"] = true}, ["debug.debug"] = {["function?"] = true, ["global?"] = true}, ["debug.gethook"] = {["function?"] = true, ["global?"] = true}, ["debug.getinfo"] = {["function?"] = true, ["global?"] = true}, ["debug.getlocal"] = {["function?"] = true, ["global?"] = true}, ["debug.getmetatable"] = {["function?"] = true, ["global?"] = true}, ["debug.getregistry"] = {["function?"] = true, ["global?"] = true}, ["debug.getupvalue"] = {["function?"] = true, ["global?"] = true}, ["debug.getuservalue"] = {["function?"] = true, ["global?"] = true}, ["debug.sethook"] = {["function?"] = true, ["global?"] = true}, ["debug.setlocal"] = {["function?"] = true, ["global?"] = true}, ["debug.setmetatable"] = {["function?"] = true, ["global?"] = true}, ["debug.setupvalue"] = {["function?"] = true, ["global?"] = true}, ["debug.setuservalue"] = {["function?"] = true, ["global?"] = true}, ["debug.traceback"] = {["function?"] = true, ["global?"] = true}, ["debug.upvalueid"] = {["function?"] = true, ["global?"] = true}, ["debug.upvaluejoin"] = {["function?"] = true, ["global?"] = true}, ["do"] = {["body-form?"] = true, ["special?"] = true}, dofile = {["function?"] = true, ["global?"] = true}, doto = {["body-form?"] = true, ["macro?"] = true}, each = {["binding-form?"] = true, ["body-form?"] = true, ["special?"] = true}, error = {["function?"] = true, ["global?"] = true}, ["eval-compiler"] = {["body-form?"] = true, ["special?"] = true}, faccumulate = {["binding-form?"] = true, ["body-form?"] = true, ["macro?"] = true}, fcollect = {["binding-form?"] = true, ["body-form?"] = true, ["macro?"] = true}, fn = {["body-form?"] = true, ["define?"] = true, ["special?"] = true}, fnl = {["global?"] = true}, ["fnl.ast-source"] = {["function?"] = true, ["global?"] = true}, ["fnl.comment"] = {["function?"] = true, ["global?"] = true}, ["fnl.comment?"] = {["function?"] = true, ["global?"] = true}, ["fnl.compile"] = {["function?"] = true, ["global?"] = true}, ["fnl.compile-stream"] = {["function?"] = true, ["global?"] = true}, ["fnl.compile-string"] = {["function?"] = true, ["global?"] = true}, ["fnl.compile1"] = {["function?"] = true, ["global?"] = true}, ["fnl.compileStream"] = {["function?"] = true, ["global?"] = true}, ["fnl.compileString"] = {["function?"] = true, ["global?"] = true}, ["fnl.doc"] = {["function?"] = true, ["global?"] = true}, ["fnl.dofile"] = {["function?"] = true, ["global?"] = true}, ["fnl.eval"] = {["function?"] = true, ["global?"] = true}, ["fnl.gensym"] = {["function?"] = true, ["global?"] = true}, ["fnl.granulate"] = {["function?"] = true, ["global?"] = true}, ["fnl.install"] = {["function?"] = true, ["global?"] = true}, ["fnl.list"] = {["function?"] = true, ["global?"] = true}, ["fnl.list?"] = {["function?"] = true, ["global?"] = true}, ["fnl.load-code"] = {["function?"] = true, ["global?"] = true}, ["fnl.loadCode"] = {["function?"] = true, ["global?"] = true}, ["fnl.make-searcher"] = {["function?"] = true, ["global?"] = true}, ["fnl.makeSearcher"] = {["function?"] = true, ["global?"] = true}, ["fnl.make_searcher"] = {["function?"] = true, ["global?"] = true}, ["fnl.mangle"] = {["function?"] = true, ["global?"] = true}, ["fnl.multi-sym?"] = {["function?"] = true, ["global?"] = true}, ["fnl.parser"] = {["function?"] = true, ["global?"] = true}, ["fnl.repl"] = {["function?"] = true, ["global?"] = true}, ["fnl.runtime-version"] = {["function?"] = true, ["global?"] = true}, ["fnl.runtimeVersion"] = {["function?"] = true, ["global?"] = true}, ["fnl.scope"] = {["function?"] = true, ["global?"] = true}, ["fnl.search-module"] = {["function?"] = true, ["global?"] = true}, ["fnl.searchModule"] = {["function?"] = true, ["global?"] = true}, ["fnl.searcher"] = {["function?"] = true, ["global?"] = true}, ["fnl.sequence"] = {["function?"] = true, ["global?"] = true}, ["fnl.sequence?"] = {["function?"] = true, ["global?"] = true}, ["fnl.string-stream"] = {["function?"] = true, ["global?"] = true}, ["fnl.stringStream"] = {["function?"] = true, ["global?"] = true}, ["fnl.sym"] = {["function?"] = true, ["global?"] = true}, ["fnl.sym-char?"] = {["function?"] = true, ["global?"] = true}, ["fnl.sym?"] = {["function?"] = true, ["global?"] = true}, ["fnl.syntax"] = {["function?"] = true, ["global?"] = true}, ["fnl.table?"] = {["function?"] = true, ["global?"] = true}, ["fnl.traceback"] = {["function?"] = true, ["global?"] = true}, ["fnl.unmangle"] = {["function?"] = true, ["global?"] = true}, ["fnl.varg"] = {["function?"] = true, ["global?"] = true}, ["fnl.varg?"] = {["function?"] = true, ["global?"] = true}, ["fnl.view"] = {["function?"] = true, ["global?"] = true}, ["for"] = {["binding-form?"] = true, ["body-form?"] = true, ["special?"] = true}, getmetatable = {["function?"] = true, ["global?"] = true}, global = {["define?"] = true, ["special?"] = true}, hashfn = {["special?"] = true}, icollect = {["binding-form?"] = true, ["body-form?"] = true, ["macro?"] = true}, ["if"] = {["special?"] = true}, ["import-macros"] = {["macro?"] = true}, include = {["special?"] = true}, io = {["global?"] = true}, ["io.close"] = {["function?"] = true, ["global?"] = true}, ["io.flush"] = {["function?"] = true, ["global?"] = true}, ["io.input"] = {["function?"] = true, ["global?"] = true}, ["io.lines"] = {["function?"] = true, ["global?"] = true}, ["io.open"] = {["function?"] = true, ["global?"] = true}, ["io.output"] = {["function?"] = true, ["global?"] = true}, ["io.popen"] = {["function?"] = true, ["global?"] = true}, ["io.read"] = {["function?"] = true, ["global?"] = true}, ["io.tmpfile"] = {["function?"] = true, ["global?"] = true}, ["io.type"] = {["function?"] = true, ["global?"] = true}, ["io.write"] = {["function?"] = true, ["global?"] = true}, ipairs = {["function?"] = true, ["global?"] = true}, lambda = {["body-form?"] = true, ["define?"] = true, ["macro?"] = true}, length = {["special?"] = true}, let = {["binding-form?"] = true, ["body-form?"] = true, ["special?"] = true}, load = {["function?"] = true, ["global?"] = true}, loadfile = {["function?"] = true, ["global?"] = true}, ["local"] = {["define?"] = true, ["special?"] = true}, lshift = {["special?"] = true}, lua = {["special?"] = true}, macro = {["body-form?"] = true, ["define?"] = true, ["macro?"] = true}, macrodebug = {["macro?"] = true}, macros = {["define?"] = true, ["special?"] = true}, match = {["body-form?"] = true, ["macro?"] = true}, ["match-try"] = {["body-form?"] = true, ["macro?"] = true}, math = {["global?"] = true}, ["math.abs"] = {["function?"] = true, ["global?"] = true}, ["math.acos"] = {["function?"] = true, ["global?"] = true}, ["math.asin"] = {["function?"] = true, ["global?"] = true}, ["math.atan"] = {["function?"] = true, ["global?"] = true}, ["math.atan2"] = {["function?"] = true, ["global?"] = true}, ["math.ceil"] = {["function?"] = true, ["global?"] = true}, ["math.cos"] = {["function?"] = true, ["global?"] = true}, ["math.cosh"] = {["function?"] = true, ["global?"] = true}, ["math.deg"] = {["function?"] = true, ["global?"] = true}, ["math.exp"] = {["function?"] = true, ["global?"] = true}, ["math.floor"] = {["function?"] = true, ["global?"] = true}, ["math.fmod"] = {["function?"] = true, ["global?"] = true}, ["math.frexp"] = {["function?"] = true, ["global?"] = true}, ["math.ldexp"] = {["function?"] = true, ["global?"] = true}, ["math.log"] = {["function?"] = true, ["global?"] = true}, ["math.log10"] = {["function?"] = true, ["global?"] = true}, ["math.max"] = {["function?"] = true, ["global?"] = true}, ["math.min"] = {["function?"] = true, ["global?"] = true}, ["math.modf"] = {["function?"] = true, ["global?"] = true}, ["math.pow"] = {["function?"] = true, ["global?"] = true}, ["math.rad"] = {["function?"] = true, ["global?"] = true}, ["math.random"] = {["function?"] = true, ["global?"] = true}, ["math.randomseed"] = {["function?"] = true, ["global?"] = true}, ["math.sin"] = {["function?"] = true, ["global?"] = true}, ["math.sinh"] = {["function?"] = true, ["global?"] = true}, ["math.sqrt"] = {["function?"] = true, ["global?"] = true}, ["math.tan"] = {["function?"] = true, ["global?"] = true}, ["math.tanh"] = {["function?"] = true, ["global?"] = true}, ["math.tointeger"] = {["function?"] = true, ["global?"] = true}, ["math.type"] = {["function?"] = true, ["global?"] = true}, ["math.ult"] = {["function?"] = true, ["global?"] = true}, next = {["function?"] = true, ["global?"] = true}, ["not"] = {["special?"] = true}, ["not="] = {["special?"] = true}, ["or"] = {["special?"] = true}, os = {["global?"] = true}, ["os.clock"] = {["function?"] = true, ["global?"] = true}, ["os.date"] = {["function?"] = true, ["global?"] = true}, ["os.difftime"] = {["function?"] = true, ["global?"] = true}, ["os.execute"] = {["function?"] = true, ["global?"] = true}, ["os.exit"] = {["function?"] = true, ["global?"] = true}, ["os.getenv"] = {["function?"] = true, ["global?"] = true}, ["os.remove"] = {["function?"] = true, ["global?"] = true}, ["os.rename"] = {["function?"] = true, ["global?"] = true}, ["os.setlocale"] = {["function?"] = true, ["global?"] = true}, ["os.time"] = {["function?"] = true, ["global?"] = true}, ["os.tmpname"] = {["function?"] = true, ["global?"] = true}, package = {["global?"] = true}, ["package.loadlib"] = {["function?"] = true, ["global?"] = true}, ["package.searchpath"] = {["function?"] = true, ["global?"] = true}, pairs = {["function?"] = true, ["global?"] = true}, partial = {["macro?"] = true}, pcall = {["function?"] = true, ["global?"] = true}, ["pick-args"] = {["macro?"] = true}, ["pick-values"] = {["macro?"] = true}, print = {["function?"] = true, ["global?"] = true}, quote = {["special?"] = true}, rawequal = {["function?"] = true, ["global?"] = true}, rawget = {["function?"] = true, ["global?"] = true}, rawlen = {["function?"] = true, ["global?"] = true}, rawset = {["function?"] = true, ["global?"] = true}, require = {["function?"] = true, ["global?"] = true}, ["require-macros"] = {["special?"] = true}, rshift = {["special?"] = true}, select = {["function?"] = true, ["global?"] = true}, set = {["special?"] = true}, ["set-forcibly!"] = {["special?"] = true}, setmetatable = {["function?"] = true, ["global?"] = true}, string = {["global?"] = true}, ["string.byte"] = {["function?"] = true, ["global?"] = true}, ["string.char"] = {["function?"] = true, ["global?"] = true}, ["string.dump"] = {["function?"] = true, ["global?"] = true}, ["string.find"] = {["function?"] = true, ["global?"] = true}, ["string.format"] = {["function?"] = true, ["global?"] = true}, ["string.gmatch"] = {["function?"] = true, ["global?"] = true}, ["string.gsub"] = {["function?"] = true, ["global?"] = true}, ["string.len"] = {["function?"] = true, ["global?"] = true}, ["string.lower"] = {["function?"] = true, ["global?"] = true}, ["string.match"] = {["function?"] = true, ["global?"] = true}, ["string.pack"] = {["function?"] = true, ["global?"] = true}, ["string.packsize"] = {["function?"] = true, ["global?"] = true}, ["string.rep"] = {["function?"] = true, ["global?"] = true}, ["string.reverse"] = {["function?"] = true, ["global?"] = true}, ["string.sub"] = {["function?"] = true, ["global?"] = true}, ["string.unpack"] = {["function?"] = true, ["global?"] = true}, ["string.upper"] = {["function?"] = true, ["global?"] = true}, table = {["global?"] = true}, ["table.concat"] = {["function?"] = true, ["global?"] = true}, ["table.insert"] = {["function?"] = true, ["global?"] = true}, ["table.move"] = {["function?"] = true, ["global?"] = true}, ["table.pack"] = {["function?"] = true, ["global?"] = true}, ["table.remove"] = {["function?"] = true, ["global?"] = true}, ["table.sort"] = {["function?"] = true, ["global?"] = true}, ["table.unpack"] = {["function?"] = true, ["global?"] = true}, tonumber = {["function?"] = true, ["global?"] = true}, tostring = {["function?"] = true, ["global?"] = true}, tset = {["special?"] = true}, type = {["function?"] = true, ["global?"] = true}, utf8 = {["global?"] = true}, ["utf8.char"] = {["function?"] = true, ["global?"] = true}, ["utf8.codepoint"] = {["function?"] = true, ["global?"] = true}, ["utf8.codes"] = {["function?"] = true, ["global?"] = true}, ["utf8.len"] = {["function?"] = true, ["global?"] = true}, ["utf8.offset"] = {["function?"] = true, ["global?"] = true}, values = {["special?"] = true}, var = {["define?"] = true, ["special?"] = true}, when = {["body-form?"] = true, ["macro?"] = true}, ["while"] = {["body-form?"] = true, ["special?"] = true}, ["with-open"] = {["binding-form?"] = true, ["body-form?"] = true, ["macro?"] = true}, xpcall = {["function?"] = true, ["global?"] = true}, ["~="] = {["special?"] = true}, ["\206\187"] = {["body-form?"] = true, ["define?"] = true, ["macro?"] = true}}

local function scan(text)
  local current_index = 1
  local last_matching_at = 0
  local text_length = string.len(text)
  local function at_eof_3f()
    return (current_index > text_length)
  end
  local function yield_buffered_nonmatching()
    if (0 < (current_index - last_matching_at)) then
      return coroutine.yield({kind = nil, value = string.sub(text, (last_matching_at + 1), (current_index - 1))})
    else
      return nil
    end
  end
  local function yield(kind, matched)
    yield_buffered_nonmatching()
    coroutine.yield({kind = kind, value = matched})
    current_index = (current_index + string.len(matched))
    last_matching_at = (current_index - 1)
    return nil
  end
  local function attempt_match(kind, pattern)
    local _2_ = string.match(text, pattern, current_index)
    if (nil ~= _2_) then
      local matched = _2_
      yield(kind, matched)
      return true
    else
      return nil
    end
  end
  local function increment_current_index()
    current_index = (current_index + 1)
    return nil
  end
  local symbol_char = "!%$&#%*%+%-%./:<=>%?%^_%w"
  while not at_eof_3f() do
    do local _ = (attempt_match("comment", "^(;[^\n]*[\n])") or attempt_match("string", "^(\"[^\"]*\")") or attempt_match("keyword", ("^(:[" .. symbol_char .. "]+)")) or attempt_match("number", "^([%+%-]?%d+[xX]?%d*%.?%d?)") or attempt_match("nil", ("^(nil)[^" .. symbol_char .. "]")) or attempt_match("boolean", ("^(true)[^" .. symbol_char .. "]")) or attempt_match("boolean", ("^(false)[^" .. symbol_char .. "]")) or attempt_match("symbol", ("^([" .. symbol_char .. "]+)")) or attempt_match("bracket", "^([%(%)%[%]{}])") or increment_current_index()) end
  end
  return yield_buffered_nonmatching()
end
local function with_symbol_subkind(syntax, _4_)
  local _arg_5_ = _4_
  local token = _arg_5_
  local kind = _arg_5_["kind"]
  local value = _arg_5_["value"]
  if ("symbol" == kind) then
    local _6_ = syntax[value]
    if ((_G.type(_6_) == "table") and ((_6_)["special?"] == true)) then
      token.subkind = "special-symbol"
    elseif ((_G.type(_6_) == "table") and ((_6_)["macro?"] == true)) then
      token.subkind = "macro-symbol"
    elseif ((_G.type(_6_) == "table") and ((_6_)["global?"] == true)) then
      token.subkind = "global-symbol"
    else
      token.subkind = nil
    end
  else
  end
  return token
end
local function token__3ehtml(_9_)
  local _arg_10_ = _9_
  local kind = _arg_10_["kind"]
  local subkind = _arg_10_["subkind"]
  local value = _arg_10_["value"]
  local class = ((kind or "nonmatching") .. " " .. (subkind or ""))
  local escaped_value = string.gsub(string.gsub(value, "&", "&amp;"), "<", "&lt;")
  return ("<span class=\"" .. class .. "\">" .. escaped_value .. "</span>")
end
local function for_html(syntax, code)
  local tokens
  do
    local tbl_17_auto = {}
    local i_18_auto = #tbl_17_auto
    local function _11_()
      return scan((code .. "\n"))
    end
    for token in coroutine.wrap(_11_) do
      local val_19_auto = token__3ehtml(with_symbol_subkind(syntax, token))
      if (nil ~= val_19_auto) then
        i_18_auto = (i_18_auto + 1)
        do end (tbl_17_auto)[i_18_auto] = val_19_auto
      else
      end
    end
    tokens = tbl_17_auto
  end
  return table.concat(tokens)
end

function highlight_fennel()
   fennel_highlighted.innerHTML = for_html(syntax, fennel_source.value)
end

function highlight_lua()
   -- TODO: Implement :-)
   lua_highlighted.innerHTML = lua_source.value
end

-- Initial run
highlight_fennel()
highlight_lua()

fennel_source.onscroll = function()
   fennel_highlighted.scrollTop = fennel_source.scrollTop
   fennel_highlighted.scrollLeft = fennel_source.scrollLeft
end

lua_source.onscroll = function()
   lua_highlighted.scrollTop = lua_source.scrollTop
   lua_highlighted.scrollLeft = lua_source.scrollLeft
end

fennel_source.oninput = function()
   highlight_fennel()
   fennel_source.onscroll()
end

lua_source.oninput = function()
   highlight_lua()
   lua_source.onscroll()
end

function update_fennel_source(new_value)
   fennel_source.value = new_value
   highlight_fennel()
end

function update_lua_source(new_value)
   lua_source.value = new_value
   highlight_lua()
end

local anti_msg = "Compiled Lua to Fennel.\n\n"..
   "Note that compiling Lua to Fennel can result in some" ..
   " strange-looking code when\nusing constructs that Fennel" ..
   " does not support natively, like early returns."

local init_worker = function(auto_click)
   -- TODO: multiple Fennel versions?
   local worker = js.new(js.global.Worker, "/see-worker.js")
   local send = function(isFennel, code)
      -- we can't send tables to workers, so we have to encode everything in
      -- strings. use an initial space for Fennel and initial tab for Lua code.
      local prefix = isFennel and " " or "\t"
      worker:postMessage(prefix .. code)
   end

   worker.onmessage = function(_, e)
      out.innerHTML = e.data -- loaded message
      -- don't set up handlers until we've loaded
      compile_fennel.onclick = function() send(true, fennel_source.value) end
      compile_lua.onclick = function() send(false, lua_source.value) end

      worker.onmessage = function(_, event)
         -- because we can't send tables as events, we encode the type of the
         -- message in the last character of the string.
         if event.data:match(" $") then
            update_lua_source(event.data)
            status("Compiled Fennel to Lua.", true)
         elseif event.data:match("\t$") then
            update_fennel_source(event.data)
            status(anti_msg, true)
         else
            status(event.data, false)
         end
      end
      if auto_click and auto_click.onclick then auto_click.onclick() end
   end
end

local load_direct = function(auto_click)
   local antifennel = dofile("antifennel.lua")
   local fennel = require("fennel")
   compile_fennel.onclick = function()
      local ok, code = pcall(fennel.compileString, fennel_source.value)
      if ok then
         update_lua_source(code)
         status("Compiled Fennel to Lua.", true)
      else
         status(tostring(code), false)
      end
   end

   compile_lua.onclick = function()
      -- for Lua that doesn't parse, antifennel gives crummy error messages
      local ok, msg = load(lua_source.value)
      if not ok then return status("Lua: " .. msg, false) end

      local ok, code = pcall(antifennel, lua_source.value)
      if ok then
         update_fennel_source(code)
         status(anti_msg, true)
      else
         status(tostring(code), false)
      end
   end

   out.innerHTML = "Loaded Fennel " .. fennel.version .. " in " .. _VERSION
   if auto_click and auto_click.onclick then auto_click.onclick() end
end

local started = false

local init = function(auto_click)
   if started then return end
   started = true
   out.innerHTML = "Loading..."

   if js.global.Worker then
      init_worker(auto_click)
   elseif js.global.setTimeout then
      js.global:setTimeout(function() load_direct(auto_click) end)
   else
      return load_direct(auto_click)
   end
end

compile_fennel.onclick = init
compile_lua.onclick = init
fennel_source.onfocus = init
lua_source.onfocus = init

--- Sample snippets

local fennel_samples = {["pong movement"]=
      ";; Read the keyboard, move player accordingly\
(local dirs {:up [0 -1] :down [0 1]\
            :left [-1 0] :right [1 0]})\
\
(each [key delta (pairs dirs)]\
  (when (love.keyboard.isDown key)\
    (let [[dx dy] delta\
          [px py] player\
          x (+ px (* dx player.speed dt))\
          y (+ py (* dy player.speed dt))]\
      (world:move player x y))))",
   fibonacci="(fn fibonacci [n]\
 (if (< n 2)\
  n\
  (+ (fibonacci (- n 1)) (fibonacci (- n 2)))))\
\
(print (fibonacci 10))",
   walk="(fn walk-tree [root f custom-iterator]\
  (fn walk [iterfn parent idx node]\
    (when (f idx node parent)\
      (each [k v (iterfn node)]\
        (walk iterfn node k v))))\
  (walk (or custom-iterator pairs) nil nil root)\
  root)"}

local lua_samples = {["sample select"]=
   "local sample_lua = document:getElementById(\"samples\")\
local lua_samples = {}\
\
for name, sample in pairs(lua_samples) do\
   local option = document:createElement(\"option\")\
   option.innerHTML = name\
   sample_lua:appendChild(option)\
end\
\
function sample_lua.onchange(self, e)\
   init()\
   local code = lua_samples[self.value]\
   if code then lua_source.value = code end\
end",
   antifennel=
   "local function uncamelize(name)\
   local function splicedash(pre, cap)\
     return pre .. \"-\" .. cap:lower()\
   end\
   return name:gsub(\"([a-z0-9])([A-Z])\", splicedash)\
end\
\
local function mangle(name, field)\
   if not field and reservedFennel[name] then\
     name = \"___\" .. name .. \"___\"\
   end\
   return field and name or\
      uncamelize(name):gsub(\"([a-z0-9])_\", \"%1-\")\
end\
\
local function compile(rdr, filename)\
   local ls = lex_setup(rdr, filename)\
   local ast_builder = lua_ast.New(mangle)\
   local ast_tree = parse(ast_builder, ls)\
   return letter(compiler(nil, ast_tree))\
end",
   ["love.run"]="function love.run()\
   love.load()\
   while true do\
      love.event.pump()\
      local needs_refresh = false\
      for name, a,b,c,d,e,f in love.event.poll() do\
         if(type(love[name]) == \"function\") then\
            love[name](a,b,c,d,e,f)\
            needs_refresh = true\
         elseif(name == \"quit\") then\
            os.exit()\
         end\
      end\
      for _,c in pairs(internal.coroutines) do\
         local ok, val = coroutine.resume(c)\
         if(ok and val) then needs_refresh = true\
         elseif(not ok) then print(val) end\
      end\
      for i,c in lume.ripairs(internal.coroutines) do\
         if(coroutine.status(c) == \"dead\") then\
            table.remove(internal.coroutines, i)\
         end\
      end\
      if(needs_refresh) then refresh() end\
      love.timer.sleep(0.05)\
   end\
end"}

local init_samples = function(id, samples, update_source)
   local select = document:getElementById(id)
   for name, sample in pairs(samples) do
      local option = document:createElement("option")
      option.innerHTML = name
      select:appendChild(option)
   end

   select.onchange = function(self, e)
      init()
      local code = samples[self.value]
      if code then update_source(code) end
   end
end

init_samples("sample-fennel", fennel_samples, update_fennel_source)
init_samples("sample-lua", lua_samples, update_lua_source)

if js.global.URLSearchParams then
   local params = js.new(js.global.URLSearchParams, document.location.search)
   local fennel_param = params:get("fennel")
   local lua_param = params:get("lua")

   if tostring(fennel_param) ~= "null" then
      update_fennel_source(fennel_param)
      init(compile_fennel)
   elseif tostring(lua_param) ~= "null" then
      update_lua_source(lua_param)
      init(compile_lua)
   end
end

D signups.fnl => signups.fnl +0 -11
@@ 1,11 0,0 @@
(local fennel (require :fennel))
(local view (require :fennelview))
(local lfs (require :lfs))

(each [filename (lfs.dir "signups")]
  (when (= :file (. (lfs.attributes (.. :signups/ filename)) :mode))
    (let [{:name name :email email :abstract abstract :confirmed confirmed}
         (fennel.dofile (.. "signups/" filename))
          name (.. name (: " " :rep (math.max 0 (- 8 (# name)))))]
      (print name email (if (and abstract (. arg 1)) abstract "")
             (if confirmed :confirmed (= confirmed false) :no)))))