1;; Copyright © 2016-2021 Calvin Rose and contributors
2;; Permission is hereby granted, free of charge, to any person obtaining a copy
3;; of this software and associated documentation files (the "Software"), to
4;; deal in the Software without restriction, including without limitation the
5;; rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
6;; sell copies of the Software, and to permit persons to whom the Software is
7;; furnished to do so, subject to the following conditions: The above copyright
8;; notice and this permission notice shall be included in all copies or
9;; substantial portions of the Software.  THE SOFTWARE IS PROVIDED "AS IS",
10;; WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
11;; TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
12;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
13;; LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
14;; CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
15;; SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16
17;; This module ties everything else together; it's the public interface of
18;; the compiler. All other modules should be considered implementation details
19;; subject to change.
20
21(local utils (require :fennel.utils))
22(local parser (require :fennel.parser))
23(local compiler (require :fennel.compiler))
24(local specials (require :fennel.specials))
25(local repl (require :fennel.repl))
26(local view (require :fennel.view))
27
28(fn eval-env [env opts]
29  (if (= env :_COMPILER)
30      (let [env (specials.make-compiler-env nil compiler.scopes.compiler {} opts)]
31        ;; re-enable globals-checking; previous globals-checking below doesn't
32        ;; work on the compiler env because of the sandbox.
33        (when (= opts.allowedGlobals nil)
34          (set opts.allowedGlobals (specials.current-global-names env)))
35        (specials.wrap-env env))
36      (and env (specials.wrap-env env))))
37
38(fn eval-opts [options str]
39  (let [opts (utils.copy options)]
40    ;; eval and dofile are considered "live" entry points, so we can assume
41    ;; that the globals available at compile time are a reasonable allowed list
42    (when (= opts.allowedGlobals nil)
43      (set opts.allowedGlobals (specials.current-global-names opts.env)))
44    ;; if the code doesn't have a filename attached, save the source in order
45    ;; to provide targeted error messages.
46    (when (and (not opts.filename) (not opts.source))
47      (set opts.source str))
48    (when (= opts.env :_COMPILER)
49      (set opts.scope (compiler.make-scope compiler.scopes.compiler)))
50    opts))
51
52(fn eval [str options ...]
53  (let [opts (eval-opts options str)
54        env (eval-env opts.env opts)
55        lua-source (compiler.compile-string str opts)
56        loader (specials.load-code lua-source env
57                                   (if opts.filename
58                                       (.. "@" opts.filename)
59                                       str))]
60    (set opts.filename nil)
61    (loader ...)))
62
63(fn dofile* [filename options ...]
64  (let [opts (utils.copy options)
65        f (assert (io.open filename :rb))
66        source (assert (f:read :*all) (.. "Could not read " filename))]
67    (f:close)
68    (set opts.filename filename)
69    (eval source opts ...)))
70
71(fn syntax []
72  "Return a table describing the callable forms known by Fennel."
73  (let [body? [:when :with-open :collect :icollect :lambda :λ
74               :macro :match :accumulate]
75        binding? [:collect :icollect :each :for :let :with-open :accumulate]
76        define? [:fn :lambda :λ :var :local :macro :macros :global]
77        out {}]
78    (each [k v (pairs compiler.scopes.global.specials)]
79      (let [metadata (or (. compiler.metadata v) {})]
80        (tset out k {:special? true :body-form? metadata.fnl/body-form?
81                     :binding-form? (utils.member? k binding?)
82                     :define? (utils.member? k define?)})))
83    (each [k v (pairs compiler.scopes.global.macros)]
84      (tset out k {:macro? true :body-form? (utils.member? k body?)
85                   :binding-form? (utils.member? k binding?)
86                   :define? (utils.member? k define?)}))
87    (each [k v (pairs _G)]
88      (match (type v)
89        :function (tset out k {:global? true :function? true})
90        :table (do
91                 (each [k2 v2 (pairs v)]
92                   (when (and (= :function (type v2)) (not= k :_G))
93                     (tset out (.. k "." k2) {:function? true :global? true})))
94                 (tset out k {:global? true}))))
95    out))
96
97;; The public API module we export:
98(local mod {:list utils.list
99            :list? utils.list?
100            :sym utils.sym
101            :sym? utils.sym?
102            :sequence utils.sequence
103            :sequence? utils.sequence?
104            :comment utils.comment
105            :comment? utils.comment?
106            :varg utils.varg
107            :path utils.path
108            :macro-path utils.macro-path
109            :sym-char? parser.sym-char?
110            :parser parser.parser
111            :granulate parser.granulate
112            :string-stream parser.string-stream
113            :compile compiler.compile
114            :compile-string compiler.compile-string
115            :compile-stream compiler.compile-stream
116            :compile1 compiler.compile1
117            :traceback compiler.traceback
118            :mangle compiler.global-mangling
119            :unmangle compiler.global-unmangling
120            :metadata compiler.metadata
121            :scope compiler.make-scope
122            :gensym compiler.gensym
123            :load-code specials.load-code
124            :macro-loaded specials.macro-loaded
125            :macro-searchers specials.macro-searchers
126            :search-module specials.search-module
127            :make-searcher specials.make-searcher
128            :makeSearcher specials.make-searcher
129            :searcher (specials.make-searcher)
130            :doc specials.doc
131            : view
132            : eval
133            :dofile dofile*
134            :version utils.version
135            : repl
136            : syntax
137            ;; backwards-compatibility aliases
138            :loadCode specials.load-code
139            :make_searcher specials.make-searcher
140            :searchModule specials.search-module
141            :macroLoaded specials.macro-loaded
142            :compileStream compiler.compile-stream
143            :compileString compiler.compile-string
144            :stringStream parser.string-stream})
145
146;; This is bad; we have a circular dependency between the specials section and
147;; the evaluation section due to require-macros/import-macros, etc. For now
148;; stash it in the utils table, but we should untangle it
149(set utils.fennel-module mod)
150
151;; Load the built-in macros from macros.fnl.
152(let [builtin-macros
153      (eval-compiler
154        (let [FENNEL_SRC (and os os.getenv (= :table (type os)) (= :function (type os.getenv))
155                              (os.getenv :FENNEL_SRC))
156              fennel-src (if FENNEL_SRC (.. FENNEL_SRC :/) "")]
157          (with-open [f (assert (io.open (.. fennel-src :src/fennel/macros.fnl)))]
158            (.. "[===[" (f:read :*all) "]===]"))))
159      module-name :fennel.macros
160      _ (tset package.preload module-name #mod)
161      env (doto (specials.make-compiler-env nil compiler.scopes.compiler {})
162            (tset :utils utils) ; for import-macros to propagate compile opts
163            (tset :fennel mod))
164      built-ins (eval builtin-macros
165                      {: env
166                       :scope compiler.scopes.compiler
167                       :allowedGlobals false
168                       :useMetadata true
169                       :filename :src/fennel/macros.fnl
170                       :moduleName module-name})]
171  (each [k v (pairs built-ins)]
172    (tset compiler.scopes.global.macros k v))
173  (set compiler.scopes.global.macros.λ compiler.scopes.global.macros.lambda)
174  (tset package.preload module-name nil))
175
176mod
177