Repository: alecthomas/chroma
Pull request #530 (2021-07-14 17:27:45)

Support Fennel.
This adds support for the Fennel programming language: https://fennel-lang.org

I couldn't find much explanation for what the different lexer rules
meant, so I based it off of Clojure's lexer since the two languages
share a very similar syntax.

I also included a program to generate a list of keywords from Fennel's
own listing, which will make it easier to update in the future.

alecthomas: Nice, thanks. Can you add some test data per the README in the lexers directory?
technomancy: Sure; I'll give it a shot. I don't have the golang tooling installed on my machine and don't really want to go thru all that, so I'll push it out to CI and see if I can generate the `.expected` file from there.
technomancy: Got some help with generating the `fennel.expected` file, however, there are a lot of "error" entries in the listing. However, there's not really any context given about what the error actually is. Could you advise?
alecthomas: An error entry means that input text wasn't matched by any of the patterns.
alecthomas: BTW you should be able to install and run go automatically via ./bin/go
technomancy: I see; so it sounds like maybe the problem is that whitespace needs to be listed explicitly then?

I was basing this off the Clojure lexer, which doesn't do this. But maybe the Clojure lexer is broken too? https://github.com/alecthomas/chroma/blob/master/lexers/c/clojure.go I don't see any tests for it so maybe it just got overlooked?
alecthomas: When I initially created Chroma there was no test data, so many lexers are missing them.

However Clojure does match whitespace [here](https://github.com/alecthomas/chroma/blob/master/lexers/c/clojure.go#L23).
technomancy: I see. But that treats whitespace as `Text`, which I think is wrong? Shouldn't it be classifying it as whitespace instead?
alecthomas: Linter is failing. I'll paste it in here so you don't have to login to Circle:

> lexers/f/fennel.go:47: File is not `gofmt`-ed with `-s` (gofmt)

Just run `./bin/gofmt -w ./lexers/f/fennel.go`
technomancy: Looks like it doesn't support my machine.

```
~/src/chroma $ ./bin/gofmt -w ./lexers/f/fennel.go
Bootstrapping /home/phil/.cache/hermit/pkg/hermit@stable/hermit from https://github.com/cashapp/hermit/releases/download/stable
Downloading https://github.com/cashapp/hermit/releases/download/stable/hermit-linux-aarch64.gz to /home/phil/.cache/hermit/pkg/hermit@stable/hermit
curl: (22) The requested URL returned error: 404 

gzip: stdin: unexpected end of file
```

Maybe I can try it on another machine later.
technomancy: Quicker to just replace the spaces with tabs manually I guess...

Patch:
diff --git a/lexers/f/fennel.go b/lexers/f/fennel.go
new file mode 100644
index 0000000..23134d4
--- /dev/null
+++ b/lexers/f/fennel.go
@@ -0,0 +1,66 @@
+package f
+
+import (
+	. "github.com/alecthomas/chroma" // nolint
+	"github.com/alecthomas/chroma/lexers/internal"
+)
+
+// Fennel lexer.
+var Fennel = internal.Register(MustNewLazyLexer(
+	&Config{
+		Name:      "Fennel",
+		Aliases:   []string{"fennel", "fnl"},
+		Filenames: []string{"*.fennel"},
+		MimeTypes: []string{"text/x-fennel", "application/x-fennel"},
+	},
+	fennelRules,
+))
+
+// Here's some Fennel code used to generate the lists of keywords:
+// (local fennel (require :fennel))
+//
+// (fn member? [t x] (each [_ y (ipairs t)] (when (= y x) (lua "return true"))))
+//
+// (local declarations [:fn :lambda :λ :local :var :global :macro :macros])
+// (local keywords [])
+// (local globals [])
+//
+// (each [name data (pairs (fennel.syntax))]
+//   (if (member? declarations name) nil ; already populated
+//       data.special? (table.insert keywords name)
+//       data.macro? (table.insert keywords name)
+//       data.global? (table.insert globals name)))
+//
+// (fn quoted [tbl]
+//   (table.sort tbl)
+//   (table.concat (icollect [_ k (ipairs tbl)]
+//                   (string.format "`%s`" k)) ", "))
+//
+// (print :Keyword (quoted keywords))
+// (print :KeywordDeclaration (quoted declarations))
+// (print :NameBuiltin (quoted globals))
+
+func fennelRules() Rules {
+	return Rules{
+		"root": {
+			{`;.*$`, CommentSingle, nil},
+			{`\s+`, Whitespace, nil},
+			{`-?\d+\.\d+`, LiteralNumberFloat, nil},
+			{`-?\d+`, LiteralNumberInteger, nil},
+			{`0x-?[abcdef\d]+`, LiteralNumberHex, nil},
+			{`"(\\\\|\\"|[^"])*"`, LiteralString, nil},
+			{`'(?!#)[\w!$%*+<=>?/.#-]+`, LiteralStringSymbol, nil},
+			{`\\(.|[a-z]+)`, LiteralStringChar, nil},
+			{`::?#?(?!#)[\w!$%*+<=>?/.#-]+`, LiteralStringSymbol, nil},
+			{"~@|[`\\'#^~&@]", Operator, nil},
+			{Words(``, ` `, `#`, `%`, `*`, `+`, `-`, `->`, `->>`, `-?>`, `-?>>`, `.`, `..`, `/`, `//`, `:`, `<`, `<=`, `=`, `>`, `>=`, `?.`, `^`, `accumulate`, `and`, `band`, `bnot`, `bor`, `bxor`, `collect`, `comment`, `do`, `doc`, `doto`, `each`, `eval-compiler`, `for`, `hashfn`, `icollect`, `if`, `import-macros`, `include`, `length`, `let`, `lshift`, `lua`, `macrodebug`, `match`, `not`, `not=`, `or`, `partial`, `pick-args`, `pick-values`, `quote`, `require-macros`, `rshift`, `set`, `set-forcibly!`, `tset`, `values`, `when`, `while`, `with-open`, `~=`), Keyword, nil},
+			{Words(``, ` `, `fn`, `global`, `lambda`, `local`, `macro`, `macros`, `var`, `λ`), KeywordDeclaration, nil},
+			{Words(``, ` `, `_G`, `arg`, `assert`, `bit32`, `bit32.arshift`, `bit32.band`, `bit32.bnot`, `bit32.bor`, `bit32.btest`, `bit32.bxor`, `bit32.extract`, `bit32.lrotate`, `bit32.lshift`, `bit32.replace`, `bit32.rrotate`, `bit32.rshift`, `collectgarbage`, `coroutine`, `coroutine.create`, `coroutine.resume`, `coroutine.running`, `coroutine.status`, `coroutine.wrap`, `coroutine.yield`, `debug`, `debug.debug`, `debug.gethook`, `debug.getinfo`, `debug.getlocal`, `debug.getmetatable`, `debug.getregistry`, `debug.getupvalue`, `debug.getuservalue`, `debug.sethook`, `debug.setlocal`, `debug.setmetatable`, `debug.setupvalue`, `debug.setuservalue`, `debug.traceback`, `debug.upvalueid`, `debug.upvaluejoin`, `dofile`, `error`, `getmetatable`, `io`, `io.close`, `io.flush`, `io.input`, `io.lines`, `io.open`, `io.output`, `io.popen`, `io.read`, `io.tmpfile`, `io.type`, `io.write`, `ipairs`, `load`, `loadfile`, `loadstring`, `math`, `math.abs`, `math.acos`, `math.asin`, `math.atan`, `math.atan2`, `math.ceil`, `math.cos`, `math.cosh`, `math.deg`, `math.exp`, `math.floor`, `math.fmod`, `math.frexp`, `math.ldexp`, `math.log`, `math.log10`, `math.max`, `math.min`, `math.modf`, `math.pow`, `math.rad`, `math.random`, `math.randomseed`, `math.sin`, `math.sinh`, `math.sqrt`, `math.tan`, `math.tanh`, `module`, `next`, `os`, `os.clock`, `os.date`, `os.difftime`, `os.execute`, `os.exit`, `os.getenv`, `os.remove`, `os.rename`, `os.setlocale`, `os.time`, `os.tmpname`, `package`, `package.loadlib`, `package.searchpath`, `package.seeall`, `pairs`, `pcall`, `print`, `rawequal`, `rawget`, `rawlen`, `rawset`, `require`, `select`, `setmetatable`, `string`, `string.byte`, `string.char`, `string.dump`, `string.find`, `string.format`, `string.gmatch`, `string.gsub`, `string.len`, `string.lower`, `string.match`, `string.rep`, `string.reverse`, `string.sub`, `string.upper`, `table`, `table.concat`, `table.insert`, `table.maxn`, `table.pack`, `table.remove`, `table.sort`, `table.unpack`, `tonumber`, `tostring`, `type`, `unpack`, `xpcall`), NameBuiltin, nil},
+			{`(?<=\()(?!#)[\w!$%*+<=>?/.#-]+`, NameFunction, nil},
+			{`(?!#)[\w!$%*+<=>?/.#-]+`, NameVariable, nil},
+			{`(\[|\])`, Punctuation, nil},
+			{`(\{|\})`, Punctuation, nil},
+			{`(\(|\))`, Punctuation, nil},
+		},
+	}
+}


Test patch:
diff --git a/lexers/testdata/fennel.actual b/lexers/testdata/fennel.actual
new file mode 100644
index 0000000..5041b7b
--- /dev/null
+++ b/lexers/testdata/fennel.actual
@@ -0,0 +1,75 @@
+;; An example of some possible linters using Fennel's --plugin option.
+
+;; The first two linters here can only function on static module
+;; use. For instance, this code can be checked because they use static
+;; field access on a local directly bound to a require call:
+
+;; (local m (require :mymodule))
+;; (print m.field) ; fails if mymodule lacks a :field field
+;; (print (m.function 1 2 3)) ; fails unless mymodule.function takes 3 args
+
+;; However, these cannot:
+
+;; (local m (do (require :mymodule)) ; m is not directly bound
+;; (print (. m field)) ; not a static field reference
+;; (let [f m.function]
+;;   (print (f 1 2 3)) ; intermediate local, not a static field call on m
+
+;; Still, pretty neat, huh?
+
+;; This file is provided as an example and is not part of Fennel's public API.
+
+(fn save-require-meta [from to scope]
+  "When destructuring, save module name if local is bound to a `require' call.
+Doesn't do any linting on its own; just saves the data for other linters."
+  (when (and (sym? to) (not (multi-sym? to)) (list? from)
+             (sym? (. from 1)) (= :require (tostring (. from 1)))
+             (= :string (type (. from 2))))
+    (let [meta (. scope.symmeta (tostring to))]
+      (set meta.required (tostring (. from 2))))))
+
+(fn check-module-fields [symbol scope]
+  "When referring to a field in a local that's a module, make sure it exists."
+  (let [[module-local field] (or (multi-sym? symbol) [])
+        module-name (-?> scope.symmeta (. (tostring module-local)) (. :required))
+        module (and module-name (require module-name))]
+    (assert-compile (or (= module nil) (not= (. module field) nil))
+                    (string.format "Missing field %s in module %s"
+                                   (or field :?) (or module-name :?)) symbol)))
+
+(fn arity-check? [module] (-?> module getmetatable (. :arity-check?)))
+
+(fn arity-check-call [[f & args] scope]
+  "Perform static arity checks on static function calls in a module."
+  (let [arity (# args)
+        last-arg (. args arity)
+        [f-local field] (or (multi-sym? f) [])
+        module-name (-?> scope.symmeta (. (tostring f-local)) (. :required))
+        module (and module-name (require module-name))]
+    (when (and (arity-check? module) _G.debug _G.debug.getinfo
+               (not (varg? last-arg)) (not (list? last-arg)))
+      (assert-compile (= (type (. module field)) :function)
+                      (string.format "Missing function %s in module %s"
+                                     (or field :?) module-name) f)
+      (match (_G.debug.getinfo (. module field))
+        {: nparams :what "Lua" :isvararg true}
+        (assert-compile (<= nparams (# args))
+                        (: "Called %s.%s with %s arguments, expected %s+"
+                           :format f-local field arity nparams) f)
+        {: nparams :what "Lua" :isvararg false}
+        (assert-compile (= nparams (# args))
+                        (: "Called %s.%s with %s arguments, expected %s"
+                           :format f-local field arity nparams) f)))))
+
+(fn check-unused [ast scope]
+  (each [symname (pairs scope.symmeta)]
+    (assert-compile (or (. scope.symmeta symname :used) (symname:find "^_"))
+                    (string.format "unused local %s" (or symname :?)) ast)))
+
+{:destructure save-require-meta
+ :symbol-to-expression check-module-fields
+ :call arity-check-call
+ ;; Note that this will only check unused args inside functions and let blocks,
+ ;; not top-level locals of a chunk.
+ :fn check-unused
+ :do check-unused}
diff --git a/lexers/testdata/fennel.expected b/lexers/testdata/fennel.expected
new file mode 100644
index 0000000..1c31bb4
--- /dev/null
+++ b/lexers/testdata/fennel.expected
@@ -0,0 +1,572 @@
+[
+  {"type":"CommentSingle","value":";; An example of some possible linters using Fennel's --plugin option."},
+  {"type":"TextWhitespace","value":"\n\n"},
+  {"type":"CommentSingle","value":";; The first two linters here can only function on static module"},
+  {"type":"TextWhitespace","value":"\n"},
+  {"type":"CommentSingle","value":";; use. For instance, this code can be checked because they use static"},
+  {"type":"TextWhitespace","value":"\n"},
+  {"type":"CommentSingle","value":";; field access on a local directly bound to a require call:"},
+  {"type":"TextWhitespace","value":"\n\n"},
+  {"type":"CommentSingle","value":";; (local m (require :mymodule))"},
+  {"type":"TextWhitespace","value":"\n"},
+  {"type":"CommentSingle","value":";; (print m.field) ; fails if mymodule lacks a :field field"},
+  {"type":"TextWhitespace","value":"\n"},
+  {"type":"CommentSingle","value":";; (print (m.function 1 2 3)) ; fails unless mymodule.function takes 3 args"},
+  {"type":"TextWhitespace","value":"\n\n"},
+  {"type":"CommentSingle","value":";; However, these cannot:"},
+  {"type":"TextWhitespace","value":"\n\n"},
+  {"type":"CommentSingle","value":";; (local m (do (require :mymodule)) ; m is not directly bound"},
+  {"type":"TextWhitespace","value":"\n"},
+  {"type":"CommentSingle","value":";; (print (. m field)) ; not a static field reference"},
+  {"type":"TextWhitespace","value":"\n"},
+  {"type":"CommentSingle","value":";; (let [f m.function]"},
+  {"type":"TextWhitespace","value":"\n"},
+  {"type":"CommentSingle","value":";;   (print (f 1 2 3)) ; intermediate local, not a static field call on m"},
+  {"type":"TextWhitespace","value":"\n\n"},
+  {"type":"CommentSingle","value":";; Still, pretty neat, huh?"},
+  {"type":"TextWhitespace","value":"\n\n"},
+  {"type":"CommentSingle","value":";; This file is provided as an example and is not part of Fennel's public API."},
+  {"type":"TextWhitespace","value":"\n\n"},
+  {"type":"Punctuation","value":"("},
+  {"type":"KeywordDeclaration","value":"fn "},
+  {"type":"NameVariable","value":"save-require-meta"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"["},
+  {"type":"NameVariable","value":"from"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"to"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"scope"},
+  {"type":"Punctuation","value":"]"},
+  {"type":"TextWhitespace","value":"\n  "},
+  {"type":"LiteralString","value":"\"When destructuring, save module name if local is bound to a `require' call.\nDoesn't do any linting on its own; just saves the data for other linters.\""},
+  {"type":"TextWhitespace","value":"\n  "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":"when "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":"and "},
+  {"type":"Punctuation","value":"("},
+  {"type":"NameFunction","value":"sym?"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"to"},
+  {"type":"Punctuation","value":")"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":"not "},
+  {"type":"Punctuation","value":"("},
+  {"type":"NameFunction","value":"multi-sym?"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"to"},
+  {"type":"Punctuation","value":"))"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"("},
+  {"type":"NameFunction","value":"list?"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"from"},
+  {"type":"Punctuation","value":")"},
+  {"type":"TextWhitespace","value":"\n             "},
+  {"type":"Punctuation","value":"("},
+  {"type":"NameFunction","value":"sym?"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":". "},
+  {"type":"NameVariable","value":"from"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"LiteralNumberInteger","value":"1"},
+  {"type":"Punctuation","value":"))"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":"= "},
+  {"type":"LiteralStringSymbol","value":":require"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"("},
+  {"type":"NameBuiltin","value":"tostring "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":". "},
+  {"type":"NameVariable","value":"from"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"LiteralNumberInteger","value":"1"},
+  {"type":"Punctuation","value":")))"},
+  {"type":"TextWhitespace","value":"\n             "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":"= "},
+  {"type":"LiteralStringSymbol","value":":string"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"("},
+  {"type":"NameBuiltin","value":"type "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":". "},
+  {"type":"NameVariable","value":"from"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"LiteralNumberInteger","value":"2"},
+  {"type":"Punctuation","value":"))))"},
+  {"type":"TextWhitespace","value":"\n    "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":"let "},
+  {"type":"Punctuation","value":"["},
+  {"type":"NameVariable","value":"meta"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":". "},
+  {"type":"NameVariable","value":"scope.symmeta"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"("},
+  {"type":"NameBuiltin","value":"tostring "},
+  {"type":"NameVariable","value":"to"},
+  {"type":"Punctuation","value":"))]"},
+  {"type":"TextWhitespace","value":"\n      "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":"set "},
+  {"type":"NameVariable","value":"meta.required"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"("},
+  {"type":"NameBuiltin","value":"tostring "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":". "},
+  {"type":"NameVariable","value":"from"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"LiteralNumberInteger","value":"2"},
+  {"type":"Punctuation","value":"))))))"},
+  {"type":"TextWhitespace","value":"\n\n"},
+  {"type":"Punctuation","value":"("},
+  {"type":"KeywordDeclaration","value":"fn "},
+  {"type":"NameVariable","value":"check-module-fields"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"["},
+  {"type":"NameVariable","value":"symbol"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"scope"},
+  {"type":"Punctuation","value":"]"},
+  {"type":"TextWhitespace","value":"\n  "},
+  {"type":"LiteralString","value":"\"When referring to a field in a local that's a module, make sure it exists.\""},
+  {"type":"TextWhitespace","value":"\n  "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":"let "},
+  {"type":"Punctuation","value":"[["},
+  {"type":"NameVariable","value":"module-local"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"field"},
+  {"type":"Punctuation","value":"]"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":"or "},
+  {"type":"Punctuation","value":"("},
+  {"type":"NameFunction","value":"multi-sym?"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"symbol"},
+  {"type":"Punctuation","value":")"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"[])"},
+  {"type":"TextWhitespace","value":"\n        "},
+  {"type":"NameVariable","value":"module-name"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":"-?\u003e "},
+  {"type":"NameVariable","value":"scope.symmeta"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":". "},
+  {"type":"Punctuation","value":"("},
+  {"type":"NameBuiltin","value":"tostring "},
+  {"type":"NameVariable","value":"module-local"},
+  {"type":"Punctuation","value":"))"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":". "},
+  {"type":"LiteralStringSymbol","value":":required"},
+  {"type":"Punctuation","value":"))"},
+  {"type":"TextWhitespace","value":"\n        "},
+  {"type":"NameBuiltin","value":"module "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":"and "},
+  {"type":"NameVariable","value":"module-name"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"("},
+  {"type":"NameBuiltin","value":"require "},
+  {"type":"NameVariable","value":"module-name"},
+  {"type":"Punctuation","value":"))]"},
+  {"type":"TextWhitespace","value":"\n    "},
+  {"type":"Punctuation","value":"("},
+  {"type":"NameFunction","value":"assert-compile"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":"or "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":"= "},
+  {"type":"NameBuiltin","value":"module "},
+  {"type":"NameVariable","value":"nil"},
+  {"type":"Punctuation","value":")"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":"not= "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":". "},
+  {"type":"NameBuiltin","value":"module "},
+  {"type":"NameVariable","value":"field"},
+  {"type":"Punctuation","value":")"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"nil"},
+  {"type":"Punctuation","value":"))"},
+  {"type":"TextWhitespace","value":"\n                    "},
+  {"type":"Punctuation","value":"("},
+  {"type":"NameBuiltin","value":"string.format "},
+  {"type":"LiteralString","value":"\"Missing field %s in module %s\""},
+  {"type":"TextWhitespace","value":"\n                                   "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":"or "},
+  {"type":"NameVariable","value":"field"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"LiteralStringSymbol","value":":?"},
+  {"type":"Punctuation","value":")"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":"or "},
+  {"type":"NameVariable","value":"module-name"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"LiteralStringSymbol","value":":?"},
+  {"type":"Punctuation","value":"))"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"symbol"},
+  {"type":"Punctuation","value":")))"},
+  {"type":"TextWhitespace","value":"\n\n"},
+  {"type":"Punctuation","value":"("},
+  {"type":"KeywordDeclaration","value":"fn "},
+  {"type":"NameVariable","value":"arity-check?"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"["},
+  {"type":"NameVariable","value":"module"},
+  {"type":"Punctuation","value":"]"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":"-?\u003e "},
+  {"type":"NameBuiltin","value":"module getmetatable "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":". "},
+  {"type":"LiteralStringSymbol","value":":arity-check?"},
+  {"type":"Punctuation","value":")))"},
+  {"type":"TextWhitespace","value":"\n\n"},
+  {"type":"Punctuation","value":"("},
+  {"type":"KeywordDeclaration","value":"fn "},
+  {"type":"NameVariable","value":"arity-check-call"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"[["},
+  {"type":"NameVariable","value":"f"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Operator","value":"\u0026"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"args"},
+  {"type":"Punctuation","value":"]"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"scope"},
+  {"type":"Punctuation","value":"]"},
+  {"type":"TextWhitespace","value":"\n  "},
+  {"type":"LiteralString","value":"\"Perform static arity checks on static function calls in a module.\""},
+  {"type":"TextWhitespace","value":"\n  "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":"let "},
+  {"type":"Punctuation","value":"["},
+  {"type":"NameVariable","value":"arity"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Operator","value":"#"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"args"},
+  {"type":"Punctuation","value":")"},
+  {"type":"TextWhitespace","value":"\n        "},
+  {"type":"NameVariable","value":"last-arg"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":". "},
+  {"type":"NameVariable","value":"args"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"arity"},
+  {"type":"Punctuation","value":")"},
+  {"type":"TextWhitespace","value":"\n        "},
+  {"type":"Punctuation","value":"["},
+  {"type":"NameVariable","value":"f-local"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"field"},
+  {"type":"Punctuation","value":"]"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":"or "},
+  {"type":"Punctuation","value":"("},
+  {"type":"NameFunction","value":"multi-sym?"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"f"},
+  {"type":"Punctuation","value":")"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"[])"},
+  {"type":"TextWhitespace","value":"\n        "},
+  {"type":"NameVariable","value":"module-name"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":"-?\u003e "},
+  {"type":"NameVariable","value":"scope.symmeta"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":". "},
+  {"type":"Punctuation","value":"("},
+  {"type":"NameBuiltin","value":"tostring "},
+  {"type":"NameVariable","value":"f-local"},
+  {"type":"Punctuation","value":"))"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":". "},
+  {"type":"LiteralStringSymbol","value":":required"},
+  {"type":"Punctuation","value":"))"},
+  {"type":"TextWhitespace","value":"\n        "},
+  {"type":"NameBuiltin","value":"module "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":"and "},
+  {"type":"NameVariable","value":"module-name"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"("},
+  {"type":"NameBuiltin","value":"require "},
+  {"type":"NameVariable","value":"module-name"},
+  {"type":"Punctuation","value":"))]"},
+  {"type":"TextWhitespace","value":"\n    "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":"when "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":"and "},
+  {"type":"Punctuation","value":"("},
+  {"type":"NameFunction","value":"arity-check?"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"module"},
+  {"type":"Punctuation","value":")"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"_G.debug"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"_G.debug.getinfo"},
+  {"type":"TextWhitespace","value":"\n               "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":"not "},
+  {"type":"Punctuation","value":"("},
+  {"type":"NameFunction","value":"varg?"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"last-arg"},
+  {"type":"Punctuation","value":"))"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":"not "},
+  {"type":"Punctuation","value":"("},
+  {"type":"NameFunction","value":"list?"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"last-arg"},
+  {"type":"Punctuation","value":")))"},
+  {"type":"TextWhitespace","value":"\n      "},
+  {"type":"Punctuation","value":"("},
+  {"type":"NameFunction","value":"assert-compile"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":"= "},
+  {"type":"Punctuation","value":"("},
+  {"type":"NameBuiltin","value":"type "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":". "},
+  {"type":"NameBuiltin","value":"module "},
+  {"type":"NameVariable","value":"field"},
+  {"type":"Punctuation","value":"))"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"LiteralStringSymbol","value":":function"},
+  {"type":"Punctuation","value":")"},
+  {"type":"TextWhitespace","value":"\n                      "},
+  {"type":"Punctuation","value":"("},
+  {"type":"NameBuiltin","value":"string.format "},
+  {"type":"LiteralString","value":"\"Missing function %s in module %s\""},
+  {"type":"TextWhitespace","value":"\n                                     "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":"or "},
+  {"type":"NameVariable","value":"field"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"LiteralStringSymbol","value":":?"},
+  {"type":"Punctuation","value":")"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"module-name"},
+  {"type":"Punctuation","value":")"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"f"},
+  {"type":"Punctuation","value":")"},
+  {"type":"TextWhitespace","value":"\n      "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":"match "},
+  {"type":"Punctuation","value":"("},
+  {"type":"NameFunction","value":"_G.debug.getinfo"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":". "},
+  {"type":"NameBuiltin","value":"module "},
+  {"type":"NameVariable","value":"field"},
+  {"type":"Punctuation","value":"))"},
+  {"type":"TextWhitespace","value":"\n        "},
+  {"type":"Punctuation","value":"{"},
+  {"type":"Keyword","value":": "},
+  {"type":"NameVariable","value":"nparams"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"LiteralStringSymbol","value":":what"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"LiteralString","value":"\"Lua\""},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"LiteralStringSymbol","value":":isvararg"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"true"},
+  {"type":"Punctuation","value":"}"},
+  {"type":"TextWhitespace","value":"\n        "},
+  {"type":"Punctuation","value":"("},
+  {"type":"NameFunction","value":"assert-compile"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":"\u003c= "},
+  {"type":"NameVariable","value":"nparams"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Operator","value":"#"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"args"},
+  {"type":"Punctuation","value":"))"},
+  {"type":"TextWhitespace","value":"\n                        "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":": "},
+  {"type":"LiteralString","value":"\"Called %s.%s with %s arguments, expected %s+\""},
+  {"type":"TextWhitespace","value":"\n                           "},
+  {"type":"LiteralStringSymbol","value":":format"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"f-local"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"field"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"arity"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"nparams"},
+  {"type":"Punctuation","value":")"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"f"},
+  {"type":"Punctuation","value":")"},
+  {"type":"TextWhitespace","value":"\n        "},
+  {"type":"Punctuation","value":"{"},
+  {"type":"Keyword","value":": "},
+  {"type":"NameVariable","value":"nparams"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"LiteralStringSymbol","value":":what"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"LiteralString","value":"\"Lua\""},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"LiteralStringSymbol","value":":isvararg"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"false"},
+  {"type":"Punctuation","value":"}"},
+  {"type":"TextWhitespace","value":"\n        "},
+  {"type":"Punctuation","value":"("},
+  {"type":"NameFunction","value":"assert-compile"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":"= "},
+  {"type":"NameVariable","value":"nparams"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Operator","value":"#"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"args"},
+  {"type":"Punctuation","value":"))"},
+  {"type":"TextWhitespace","value":"\n                        "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":": "},
+  {"type":"LiteralString","value":"\"Called %s.%s with %s arguments, expected %s\""},
+  {"type":"TextWhitespace","value":"\n                           "},
+  {"type":"LiteralStringSymbol","value":":format"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"f-local"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"field"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"arity"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"nparams"},
+  {"type":"Punctuation","value":")"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"f"},
+  {"type":"Punctuation","value":")))))"},
+  {"type":"TextWhitespace","value":"\n\n"},
+  {"type":"Punctuation","value":"("},
+  {"type":"KeywordDeclaration","value":"fn "},
+  {"type":"NameVariable","value":"check-unused"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"["},
+  {"type":"NameVariable","value":"ast"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"scope"},
+  {"type":"Punctuation","value":"]"},
+  {"type":"TextWhitespace","value":"\n  "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":"each "},
+  {"type":"Punctuation","value":"["},
+  {"type":"NameVariable","value":"symname"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"("},
+  {"type":"NameBuiltin","value":"pairs "},
+  {"type":"NameVariable","value":"scope.symmeta"},
+  {"type":"Punctuation","value":")]"},
+  {"type":"TextWhitespace","value":"\n    "},
+  {"type":"Punctuation","value":"("},
+  {"type":"NameFunction","value":"assert-compile"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":"or "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":". "},
+  {"type":"NameVariable","value":"scope.symmeta"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"symname"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"LiteralStringSymbol","value":":used"},
+  {"type":"Punctuation","value":")"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"("},
+  {"type":"NameFunction","value":"symname"},
+  {"type":"LiteralStringSymbol","value":":find"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"LiteralString","value":"\"^_\""},
+  {"type":"Punctuation","value":"))"},
+  {"type":"TextWhitespace","value":"\n                    "},
+  {"type":"Punctuation","value":"("},
+  {"type":"NameBuiltin","value":"string.format "},
+  {"type":"LiteralString","value":"\"unused local %s\""},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"("},
+  {"type":"Keyword","value":"or "},
+  {"type":"NameVariable","value":"symname"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"LiteralStringSymbol","value":":?"},
+  {"type":"Punctuation","value":"))"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"ast"},
+  {"type":"Punctuation","value":")))"},
+  {"type":"TextWhitespace","value":"\n\n"},
+  {"type":"Punctuation","value":"{"},
+  {"type":"LiteralStringSymbol","value":":destructure"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"save-require-meta"},
+  {"type":"TextWhitespace","value":"\n "},
+  {"type":"LiteralStringSymbol","value":":symbol-to-expression"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"check-module-fields"},
+  {"type":"TextWhitespace","value":"\n "},
+  {"type":"LiteralStringSymbol","value":":call"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"arity-check-call"},
+  {"type":"TextWhitespace","value":"\n "},
+  {"type":"CommentSingle","value":";; Note that this will only check unused args inside functions and let blocks,"},
+  {"type":"TextWhitespace","value":"\n "},
+  {"type":"CommentSingle","value":";; not top-level locals of a chunk."},
+  {"type":"TextWhitespace","value":"\n "},
+  {"type":"LiteralStringSymbol","value":":fn"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"check-unused"},
+  {"type":"TextWhitespace","value":"\n "},
+  {"type":"LiteralStringSymbol","value":":do"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"check-unused"},
+  {"type":"Punctuation","value":"}"},
+  {"type":"TextWhitespace","value":"\n"}
+]