~technomancy/fennel

ref: 83ccb3c3345e84c59303fcd97db257d533905399 fennel/src/launcher.fnl -rw-r--r-- 9.6 KiB
83ccb3c3Phil Hagelberg Fix apropos tests for other Lua versions. 6 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
;; This is the command-line entry point for Fennel.

(local fennel (require :fennel))
(local unpack (or table.unpack _G.unpack))

(local help "
Usage: fennel [FLAG] [FILE]

Run fennel, a lisp programming language for the Lua runtime.

  --repl                  : Command to launch an interactive repl session
  --compile FILES         : Command to AOT compile files, writing Lua to stdout
  --eval SOURCE (-e)      : Command to evaluate source code and print the result

  --no-searcher           : Skip installing package.searchers entry
  --indent VAL            : Indent compiler output with VAL
  --add-package-path PATH : Add PATH to package.path for finding Lua modules
  --add-fennel-path  PATH : Add PATH to fennel.path for finding Fennel modules
  --globals G1[,G2...]    : Allow these globals in addition to standard ones
  --globals-only G1[,G2]  : Same as above, but exclude standard ones
  --require-as-include    : Inline required modules in the output
  --use-bit-lib           : Use LuaJITs bit library instead of operators
  --metadata              : Enable function metadata, even in compiled output
  --no-metadata           : Disable function metadata, even in REPL
  --correlate             : Make Lua output line numbers match Fennel input
  --load FILE (-l)        : Load the specified FILE before executing the command
  --lua LUA_EXE           : Run in a child process with LUA_EXE
  --no-fennelrc           : Skip loading ~/.fennelrc when launching repl
  --plugin FILE           : Activate the compiler plugin in FILE
  --compile-binary FILE
      OUT LUA_LIB LUA_DIR : Compile FILE to standalone binary OUT
  --compile-binary --help : Display further help for compiling binaries
  --no-compiler-sandbox   : Do not limit compiler environment to minimal sandbox

  --help (-h)             : Display this text
  --version (-v)          : Show version

  Globals are not checked when doing AOT (ahead-of-time) compilation unless
  the --globals-only flag is provided.

  Metadata is typically considered a development feature and is not recommended
  for production. It is used for docstrings and enabled by default in the REPL.

  When not given a command, runs the file given as the first argument.
  When given neither command nor file, launches a repl.

  If ~/.fennelrc exists, loads it before launching a repl.")

(local options {:plugins []})

(fn dosafely [f ...]
  (let [args [...]
        (ok val) (xpcall #(f (unpack args)) fennel.traceback)]
    (when (not ok)
      (io.stderr:write (.. val "\n"))
      (os.exit 1))
    val))

(fn allow-globals [global-names]
  (set options.allowedGlobals [])
  (each [g (global-names:gmatch "([^,]+),?")]
    (table.insert options.allowedGlobals g)))

(fn handle-load [i]
  (let [file (table.remove arg (+ i 1))]
    (dosafely fennel.dofile file options)
    (table.remove arg i)))

(fn handle-lua [i]
  (table.remove arg i) ; remove the --lua flag from args
  (let [tgt-lua (table.remove arg i)
        cmd [(string.format "%s %s" tgt-lua (. arg 0))]]
    (for [i 1 (length arg)] ; quote args to prevent shell escapes when executing
      (table.insert cmd (string.format "%q" (. arg i))))
    (let [ok (os.execute (table.concat cmd " "))]
      (os.exit (if ok 0 1) true))))

;; check for --lua first to ensure its child process retains all flags
(for [i (length arg) 1 -1]
  (match (. arg i)
    :--lua (handle-lua i)))

(for [i (length arg) 1 -1]
  (match (. arg i)
    :--no-searcher (do
                     (set options.no-searcher true)
                     (table.remove arg i))
    :--indent (do
                (set options.indent (table.remove arg (+ i 1)))
                (when (= options.indent :false)
                  (set options.indent false))
                (table.remove arg i))
    :--add-package-path (let [entry (table.remove arg (+ i 1))]
                          (set package.path (.. entry ";" package.path))
                          (table.remove arg i))
    :--add-fennel-path (let [entry (table.remove arg (+ i 1))]
                         (set fennel.path (.. entry ";" fennel.path))
                         (table.remove arg i))
    :--load (handle-load i)
    :-l (handle-load i)
    :--no-fennelrc (do
                     (set options.fennelrc false)
                     (table.remove arg i))
    :--correlate (do
                   (set options.correlate true)
                   (table.remove arg i))
    :--check-unused-locals (do
                             (set options.checkUnusedLocals true)
                             (table.remove arg i))
    :--globals (do
                 (allow-globals (table.remove arg (+ i 1)))
                 (each [global-name (pairs _G)]
                   (table.insert options.allowedGlobals global-name))
                 (table.remove arg i))
    :--globals-only (do
                      (allow-globals (table.remove arg (+ i 1)))
                      (table.remove arg i))
    :--require-as-include (do
                            (set options.requireAsInclude true)
                            (table.remove arg i))
    :--use-bit-lib (do
                     (set options.useBitLib true)
                     (table.remove arg i))
    :--metadata (do
                  (set options.useMetadata true)
                  (table.remove arg i))
    :--no-metadata (do
                     (set options.useMetadata false)
                     (table.remove arg i))
    :--no-compiler-sandbox (do
                             (set options.compiler-env _G)
                             (table.remove arg i))
    :--plugin (let [plugin (fennel.dofile (table.remove arg (+ i 1))
                                          {:env :_COMPILER :useMetadata true})]
                (table.insert options.plugins 1 plugin)
                (table.remove arg i))))

(local searcher-opts {})

(when (not options.no-searcher)
  (each [k v (pairs options)]
    (tset searcher-opts k v))
  (table.insert (or package.loaders package.searchers)
                (fennel.make-searcher searcher-opts)))

(fn try-readline [ok readline]
  (when ok
    (when readline.set_readline_name
      (readline.set_readline_name :fennel))
    (readline.set_options {:keeplines 1000 :histfile ""})

    (fn options.readChunk [parser-state]
      (let [prompt (if (< 0 parser-state.stack-size) ".. " ">> ")
            str (readline.readline prompt)]
        (if str (.. str "\n"))))

    (var completer nil)

    (fn options.registerCompleter [repl-completer]
      (set completer repl-completer))

    (fn repl-completer [text from to]
      (if completer
          (do
            (readline.set_completion_append_character "")
            (completer (text:sub from to)))
          []))

    (readline.set_complete_function repl-completer)
    readline))

(fn load-initfile []
  (let [home (or (os.getenv :HOME) "/")
        xdg-config-home (or (os.getenv :XDG_CONFIG_HOME) (.. home :/.config))
        xdg-initfile (.. xdg-config-home :/fennel/fennelrc)
        home-initfile (.. home :/.fennelrc)
        init (io.open xdg-initfile :rb)
        init-filename (if init xdg-initfile home-initfile)
        init (or init (io.open home-initfile :rb))]
    (when init
      (init:close)
      (dosafely fennel.dofile init-filename options options fennel))))

(fn repl []
  (let [readline (and (not= "dumb" (os.getenv "TERM"))
                      (try-readline (pcall require :readline)))]
    (set searcher-opts.useMetadata (not= false options.useMetadata))
    (set options.pp (require :fennel.view))
    (when (not= false options.fennelrc)
      (load-initfile))
    (print (.. "Welcome to Fennel " fennel.version " on " _VERSION "!"))
    (print "Use ,help to see available commands.")
    (when (and (not readline) (not= "dumb" (os.getenv "TERM")))
      (print "Try installing readline via luarocks for a better repl experience."))
    (fennel.repl options)
    (when readline
      (readline.save_history))))

(fn eval [form]
  (print (dosafely fennel.eval (if (= form "-")
                                   (io.stdin:read :*a)
                                   form) options)))

(match arg
  ([] ? (= 0 (length arg))) (repl)
  [:--repl] (repl)
  [:--compile & files] (each [_ filename (ipairs files)]
                         (set options.filename filename)
                         (let [f (if (= filename "-")
                                     io.stdin
                                     (assert (io.open filename :rb)))
                               (ok val) (xpcall #(fennel.compile-string (f:read :*a)
                                                                        options)
                                                fennel.traceback)]
                           (if ok
                               (print val)
                               (do
                                 (io.stderr:write (.. val "\n"))
                                 (os.exit 1)))
                           (f:close)))
  [:--compile-binary filename out static-lua lua-include-dir & args]
  (let [bin (require :fennel.binary)]
    (set options.filename filename)
    (set options.requireAsInclude true)
    (bin.compile filename out static-lua lua-include-dir options args))
  [:--compile-binary] (print (. (require :fennel.binary) :help))
  [:--eval form] (eval form)
  [:-e form] (eval form)
  ([a] ? (or (= a :-v) (= a :--version)))
  (print (.. "Fennel " fennel.version " on " _VERSION))
  [:--help] (print help)
  [:-h] (print help)
  ["-" & args] (dosafely fennel.eval (io.stdin:read :*a))
  [filename & args] (do
                      (tset arg -2 (. arg -1))
                      (tset arg -1 (. arg 0))
                      (tset arg 0 (table.remove arg 1))
                      (dosafely fennel.dofile filename options (unpack args))))