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