~technomancy/fennel

ref: 83ccb3c3345e84c59303fcd97db257d533905399 fennel/src/fennel.fnl -rw-r--r-- 7.5 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
;; Copyright © 2016-2021 Calvin Rose and contributors
;; Permission is hereby granted, free of charge, to any person obtaining a copy
;; of this software and associated documentation files (the "Software"), to
;; deal in the Software without restriction, including without limitation the
;; rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
;; sell copies of the Software, and to permit persons to whom the Software is
;; furnished to do so, subject to the following conditions: The above copyright
;; notice and this permission notice shall be included in all copies or
;; substantial portions of the Software.  THE SOFTWARE IS PROVIDED "AS IS",
;; WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
;; TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
;; LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
;; CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
;; SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

;; This module ties everything else together; it's the public interface of
;; the compiler. All other modules should be considered implementation details
;; subject to change.

(local utils (require :fennel.utils))
(local parser (require :fennel.parser))
(local compiler (require :fennel.compiler))
(local specials (require :fennel.specials))
(local repl (require :fennel.repl))
(local view (require :fennel.view))

(fn eval-env [env opts]
  (if (= env :_COMPILER)
      (let [env (specials.make-compiler-env nil compiler.scopes.compiler {})]
        ;; re-enable globals-checking; previous globals-checking below doesn't
        ;; work on the compiler env because of the sandbox.
        (when (= opts.allowedGlobals nil)
          (set opts.allowedGlobals (specials.current-global-names env)))
        (specials.wrap-env env))
      (and env (specials.wrap-env env))))

(fn eval-opts [options str]
  (let [opts (utils.copy options)]
    ;; eval and dofile are considered "live" entry points, so we can assume
    ;; that the globals available at compile time are a reasonable allowed list
    (when (= opts.allowedGlobals nil)
      (set opts.allowedGlobals (specials.current-global-names opts.env)))
    ;; if the code doesn't have a filename attached, save the source in order
    ;; to provide targeted error messages.
    (when (and (not opts.filename) (not opts.source))
      (set opts.source str))
    (when (= opts.env :_COMPILER)
      (set opts.scope (compiler.make-scope compiler.scopes.compiler)))
    opts))

(fn eval [str options ...]
  (let [opts (eval-opts options str)
        env (eval-env opts.env opts)
        lua-source (compiler.compile-string str opts)
        loader (specials.load-code lua-source env
                                   (if opts.filename
                                       (.. "@" opts.filename)
                                       str))]
    (set opts.filename nil)
    (loader ...)))

(fn dofile* [filename options ...]
  (let [opts (utils.copy options)
        f (assert (io.open filename :rb))
        source (assert (f:read :*all) (.. "Could not read " filename))]
    (f:close)
    (set opts.filename filename)
    (eval source opts ...)))

(fn syntax []
  "Return a table describing the callable forms known by Fennel."
  (let [body? [:when :with-open :collect :icollect :lambda  :macro :match]
        binding? [:collect :icollect :each :for :let :with-open]
        define? [:fn :lambda  :var :local :macro :macros :global]
        out {}]
    (each [k v (pairs compiler.scopes.global.specials)]
      (let [metadata (or (. compiler.metadata v) {})]
        (tset out k {:special? true :body-form? metadata.fnl/body-form?
                     :binding-form? (utils.member? k binding?)
                     :define? (utils.member? k define?)})))
    (each [k v (pairs compiler.scopes.global.macros)]
      (tset out k {:macro? true :body-form? (utils.member? k body?)
                   :binding-form? (utils.member? k binding?)
                   :define? (utils.member? k define?)}))
    (each [k v (pairs _G)]
      (match (type v)
        :function (tset out k {:global? true :function? true})
        :table (do
                 (each [k2 v2 (pairs v)]
                   (when (and (= :function (type v2)) (not= k :_G))
                     (tset out (.. k "." k2) {:function? true :global? true})))
                 (tset out k {:global? true}))))
    out))

;; The public API module we export:
(local mod {:list utils.list
            :list? utils.list?
            :sym utils.sym
            :sym? utils.sym?
            :sequence utils.sequence
            :sequence? utils.sequence?
            :comment utils.comment
            :comment? utils.comment?
            :varg utils.varg
            :path utils.path
            :macro-path utils.macro-path
            :sym-char? parser.sym-char?
            :parser parser.parser
            :granulate parser.granulate
            :string-stream parser.string-stream
            :compile compiler.compile
            :compile-string compiler.compile-string
            :compile-stream compiler.compile-stream
            :compile1 compiler.compile1
            :traceback compiler.traceback
            :mangle compiler.global-mangling
            :unmangle compiler.global-unmangling
            :metadata compiler.metadata
            :scope compiler.make-scope
            :gensym compiler.gensym
            :load-code specials.load-code
            :macro-loaded specials.macro-loaded
            :macro-searchers specials.macro-searchers
            :search-module specials.search-module
            :make-searcher specials.make-searcher
            :makeSearcher specials.make-searcher
            :searcher (specials.make-searcher)
            :doc specials.doc
            : view
            : eval
            :dofile dofile*
            :version :0.10.0-dev
            : repl
            : syntax
            ;; backwards-compatibility aliases
            :loadCode specials.load-code
            :make_searcher specials.make-searcher
            :searchModule specials.search-module
            :macroLoaded specials.macro-loaded
            :compileStream compiler.compile-stream
            :compileString compiler.compile-string
            :stringStream parser.string-stream})

;; This is bad; we have a circular dependency between the specials section and
;; the evaluation section due to require-macros/import-macros, etc. For now
;; stash it in the utils table, but we should untangle it
(set utils.fennel-module mod)

;; Load the built-in macros from macros.fnl.
(let [builtin-macros (eval-compiler
                       (with-open [f (assert (io.open :src/fennel/macros.fnl))]
                         (.. "[===[" (f:read :*all) "]===]")))
      module-name :fennel.macros
      _ (tset package.preload module-name #mod)
      env (doto (specials.make-compiler-env nil compiler.scopes.compiler {})
            (tset :utils utils) ; for import-macros to propagate compile opts
            (tset :fennel mod))
      built-ins (eval builtin-macros
                      {: env
                       :scope compiler.scopes.compiler
                       :allowedGlobals false
                       :useMetadata true
                       :filename :src/fennel/macros.fnl
                       :moduleName module-name})]
  (each [k v (pairs built-ins)]
    (tset compiler.scopes.global.macros k v))
  (set compiler.scopes.global.macros.λ compiler.scopes.global.macros.lambda)
  (tset package.preload module-name nil))

mod