rewrite-based desugar pass

This commit is contained in:
Jeremy Penner 2024-07-13 22:35:43 -04:00
parent 34a715db2f
commit f7fd4745b7
4 changed files with 123 additions and 11 deletions

View file

@ -1,9 +1,25 @@
(ns tock.compiler (ns tock.compiler
(:require [tock.compiler.util :refer [form-dispatch compile-error lookup bind!] :as u] (:require [tock.compiler.util :refer [form-dispatch compile-error lookup bind!] :as u]
[tock.compiler.type :refer [typecheck-expr]] [tock.compiler.type :refer [typecheck-expr]]
[tock.compiler.wasm :refer [ctx-to-wasm]] [tock.compiler.wasm :refer [ctx-to-wasm]]
[meander.epsilon :as m])) [meander.epsilon :as m]))
;; compiler structure:
;; a quoted form is passed through a series of passes:
;; 1. canonicalization pass
;; * remove namespaces from symbols (allows clojure code to generate code using quasiquote / unquote)
;; * wrap literals in ('tock/lit) forms
;; 2. expansion pass
;; * recursively desugars expressions until the entire source tree is in the form of (special expr...)
;; * any non-typed parameters (names, types, etc) are stored in metadata
;; 3. typechecking pass
;; * types are calculated for each expression in the tree, bottom-up
;; * parents use `coerce` or `unify` to validate each subexpression
;; 4. codegen pass
;; * function expression trees are recursively walked to generate linear wasm bytecode
;; * tock types are lowered to wasm types?
(defmulti compile-toplevel (fn [form _ctx] (form-dispatch form))) (defmulti compile-toplevel (fn [form _ctx] (form-dispatch form)))
(defmethod compile-toplevel :default [form _ctx] (defmethod compile-toplevel :default [form _ctx]
(throw (compile-error form "Unrecognized form"))) (throw (compile-error form "Unrecognized form")))

View file

@ -0,0 +1,74 @@
(ns tock.compiler.desugar
(:require [meander.epsilon :as m]
[meander.strategy.epsilon :as r]
[tock.compiler.meander :refer [meta-split label typed m+]]))
;; desugaring idea:
;; - each parameter to a special is decorated with a human-readable descriptor
;; as well as the original syntax object it corresponds to
(defn preserving-meta [strategy]
(fn [term]
(let [new-term (strategy term)]
(m+ (merge (meta new-term) (meta term)) new-term))))
;; no namespace - source symbol
;; l/sym - "lowered" form - special form not directly writable from source
;; i/sym - intermediate symbol meant to be recursively expanded into a canonical
;; lowered form
(def leaf-pass
(r/bottom-up
(preserving-meta
(r/attempt
(r/rewrite
(m/pred symbol? ?sym) (m/app #(symbol (name %)) ?sym)
(m/pred number? ?num) ('l/lit ?num)
(m/pred boolean? ?b) (typed ('l/lit ?b) 'bool))))))
(def type-cleanup-pass
(r/bottom-up
(r/attempt
(r/rewrite
(meta-split {:tag (m/some ?symbol)} ?form)
(typed ?form ?symbol)
(meta-split {:fn [!type ... '-> ?return-type]} ?form)
(typed ?form ['fn !type ... ?return-type])
(meta-split {:fn [!type ...]} ?form)
(typed ?form ['fn !type ... 'void])))))
(defn make-desugar-pass [sugars]
(r/top-down
(r/until =
(preserving-meta
(apply r/pipe (map r/attempt sugars))))))
(def tock-sugars
[(r/rewrite
('if ?cond ?body & ?more) (i/if ?cond ?body & ?more)
(i/if) ('do)
(i/if ?else) (label ?else "else block")
(i/if ?cond ?body & ?more) (l/if (label ?cond "condition")
(label ?body "body")
(i/if & ?more)))
(r/rewrite
('fn (m/pred symbol? ?name) & ?rest) ('def ?name ('fn & ?rest))
('fn [!names ... '-> ?return-type] & ?body) (l/fn [!names ... (label ?return-type "return type")] & ?body)
('fn [!names ...] & ?body) (l/fn [!names ... 'void] & ?body))
])
(def tock-parse-errors
[(r/rewrite ('if) "if statement needs at least a condition and a body"
('if _) "if statement needs at least one body expression")
])
(defn desugar
([form] (desugar form tock-sugars))
([form sugars]
(let [desugar-pass (make-desugar-pass sugars)]
(-> form
leaf-pass
type-cleanup-pass
desugar-pass))))

View file

@ -0,0 +1,20 @@
(ns tock.compiler.meander
(:require [meander.epsilon :as m]
[meander.syntax.epsilon :as r.syntax]))
(defn m+ [more-meta val]
(if (satisfies? #?(:clj clojure.lang.IObj :cljs IWithMeta) val)
(with-meta val (merge (meta val) more-meta))
val))
(m/defsyntax meta-split [meta-pattern pattern]
(case (::r.syntax/phase &env)
:meander/substitute `(m/app m+ ~meta-pattern ~pattern)
:meander/match `(m/and (m/app meta ~meta-pattern) ~pattern)
&form))
(m/defsyntax label [form label]
`(meta-split {:label ~label} ~form))
(m/defsyntax typed [form type]
`(meta-split {:type ~type} ~form))

View file

@ -1,16 +1,17 @@
(ns tock.experiment (ns tock.experiment
(:require [helins.wasm :as wasm] (:require [helins.wasm :as wasm]
[helins.binf :as binf] [helins.binf :as binf]
[tock.compiler.desugar :refer [desugar]]
[tock.compiler :refer [compile]])) [tock.compiler :refer [compile]]))
;; ; https://github.com/kalai-transpiler/kalai ;; ; https://github.com/kalai-transpiler/kalai
(def test-wasm ;; (def test-wasm
(compile ;; (compile
'[(defn add [^f64 left ^f64 right -> f64] (+ left right)) ;; '[(defn add [^f64 left ^f64 right -> f64] (+ left right))
(defn double [^f64 val -> f64] (* val 2)) ;; (defn double [^f64 val -> f64] (* val 2))
(defn add_double [^f64 left ^f64 right -> f64] (double (add left right)))])) ;; (defn add_double [^f64 left ^f64 right -> f64] (double (add left right)))]))
(defn decompile-url [url] (defn decompile-url [url]
(-> (js/fetch url) (-> (js/fetch url)
@ -26,8 +27,9 @@
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]} #_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
(defn main [] (defn main []
(js/console.log test-wasm) (js/console.log (desugar `(if true (thing bloop) false (blarp poop)))))
(-> (instantiate-wasm test-wasm #js {}) ;; (js/console.log test-wasm)
(.then #(js/console.log (-> % (.-instance) (.-exports) (.add-double 2 3))))) ;; (-> (instantiate-wasm test-wasm #js {})
(-> (decompile-url "release.wasm") ;; (.then #(js/console.log (-> % (.-instance) (.-exports) (.add-double 2 3)))))
(.then #(js/console.log (-> % :wasm/exportsec :wasm.export/func (get 0)))))) ;; (-> (decompile-url "release.wasm")
;; (.then #(js/console.log (-> % :wasm/exportsec :wasm.export/func (get 0))))))