changed my mind; desugar expressions into (special metadata subexpr...)

started adding typechecking logic; still need to reimplement
typechecking pass itself
This commit is contained in:
Jeremy Penner 2024-07-22 22:36:51 -04:00
parent a362729acc
commit 6c6d674c2a
4 changed files with 137 additions and 91 deletions

View file

@ -2,60 +2,56 @@
(:require [meander.epsilon :as m]
[meander.strategy.epsilon :as r]
[tock.compiler.specials :refer [specials]]
[tock.compiler.meander :refer [meta-split typed label preserving-meta]]))
[tock.compiler.meander :refer [parse-type to-sym label] :include-macros true]))
(def leaf-pass
(r/bottom-up
(preserving-meta
(r/pipe
;; all symbols must have their namespaces stripped!
(r/bottom-up
(r/attempt
(r/rewrite
(m/pred symbol? ?sym) (m/app #(symbol (name %)) ?sym)
(m/pred integer? ?num) (typed (l/lit ?num) 'i64)
(m/pred double? ?num) (typed (l/lit ?num) 'f64)
(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 (m/app #(symbol (name %)) ?symbol))
(meta-split {:fn [!type ... '-> ?return-type]} ?form)
(typed ?form ['fn . !type ... ?return-type])
(meta-split {:fn [!type ...]} ?form)
(typed ?form ['fn . !type ... 'void])))))
(m/pred symbol? ?sym)
(m/app #(with-meta (to-sym %) (merge (meta %) {:form %})) ?sym))))
(r/rewrite
(m/pred integer? ?num) ('l/lit {:type 'i64 :value ?num :form ?num})
(m/pred double? ?num) ('l/lit {:type 'f64 :value ?num :form ?num})
(m/pred boolean? ?b) ('l/lit {:type 'bool :value ?b :form ?b})
(m/and ((m/cata ?verb) . (m/cata !nouns) ...) (m/app meta ?m) ?f)
(?verb (m/app merge (m/app parse-type ?m) {:form ?f}) . !nouns ...)
; [vectors] and {maps} are _not_ expressions and are parsed by special form's desugaring only
?syntax ?syntax)))
(defn make-desugar-pass [specials]
(r/top-down
(r/until =
(preserving-meta
(apply r/pipe (mapcat (fn [[_ {:keys [desugar]}]] (if desugar [(r/attempt desugar)] [])) specials))))))
(let [desugar-strat (apply r/pipe (mapcat (fn [[_ {:keys [desugar]}]] (if desugar [(r/attempt desugar)] [])) specials))]
(r/until =
(r/rewrite
((m/cata ?special) ?m . (m/cata !args) ...)
(m/app desugar-strat (?special ?m . !args ...))
?form ?form))))
(defn make-structure-pass [specials]
(defn make-call-pass [specials]
(let [special? (fn [key] (and (symbol? key) (contains? specials key)))]
(r/rewrite
(meta-split ?meta (m/pred symbol? ?sym))
(m/app merge ?meta {:special 'l/read :name ?sym})
(m/and (m/pred symbol? ?sym) (m/app meta ?m)) ('l/read {:symbol ?sym & ?m})
((m/pred special? ?special) ?m . (m/cata !args) ...)
(?special ?m . !args ...)
(meta-split ?meta ('l/lit ?val))
(m/app merge ?meta {:special 'l/lit :val ?val})
(meta-split ?meta ((m/pred special? ?special) . (m/cata !args) ...))
(m/app merge ?meta {:special ?special :args [!args ...]})
(meta-split ?meta ((m/pred #(not (special? %)) (m/cata ?func)) . (m/cata !args) ...))
(m/app merge ?meta {:special 'l/call :args [(label ?func "function") . (label !args "argument") ...]}))))
((m/cata ?func) ?m . (m/cata !args) ...)
('l/call ?m (label ?func "function") . (label !args "argument") ...)
?unknown ('l/compile-error {:form ?unknown :error "Unrecognized form"}))))
(defn desugar
([form] (desugar form specials))
([form specials]
(let [desugar-pass (make-desugar-pass specials)
structure-pass (make-structure-pass specials)]
call-pass (make-call-pass specials)]
(-> form
leaf-pass
type-cleanup-pass
desugar-pass
structure-pass
call-pass
))))

View file

@ -1,25 +1,39 @@
(ns tock.compiler.meander
(:require [meander.epsilon :as m]
(:require [meander.epsilon :as m]
[meander.strategy.epsilon :as r]
[meander.syntax.epsilon :as r.syntax]))
(defn m+ [more-meta val]
(with-meta val (merge (meta val) more-meta)))
(defn merge-metafield [form m]
(cond
(symbol? form) (with-meta form (merge (meta form) m))
(list? form) (apply list (first form) (merge (second form) m) (rest (rest form)))
:else (do (print "m+ " form meta "\n") form)))
(defn preserving-meta [strategy]
(fn [term]
(let [new-term (strategy term)]
(if (satisfies? #?(:clj clojure.lang.IObj :cljs IWithMeta) new-term)
(with-meta new-term (merge (meta term) (meta new-term)))
new-term))))
(m/defsyntax meta-split [meta-pattern pattern]
(m/defsyntax m+ [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)
:meander/substitute `(m/app merge-metafield ~pattern ~meta-pattern)
:meander/match `(m/and (_ ~meta-pattern . _ ...) ~pattern)
&form))
(m/defsyntax label [form label]
`(meta-split {:label ~label} ~form))
`(m+ {:label ~label} ~form))
(m/defsyntax typed [form type]
`(meta-split {:type ~type} ~form))
`(m+ {:type ~type} ~form))
(defn to-sym [ns-sym] (symbol (name ns-sym)))
(def parse-type
(r/attempt
(r/rewrite
(m/pred symbol? ?symbol)
(m/app merge (m/app meta ?symbol) {:type ?symbol})
{:tag (m/some ?symbol) & ?m}
{:type (m/app to-sym ?symbol) & ?m}
{(m/app to-sym 'fn) [!type ... (m/app to-sym '->) ?return-type] & ?m}
{:type ['fn . !type ... ?return-type] & ?m}
{(m/app to-sym 'fn) [!type ...] & ?m}
{:type ['fn . !type ... 'void] & ?m})))

View file

@ -1,7 +1,7 @@
(ns tock.compiler.specials
(:require [meander.epsilon :as m]
[meander.strategy.epsilon :as r]
[tock.compiler.meander :refer [meta-split label typed]]))
[tock.compiler.meander :refer [parse-type to-sym label] :include-macros true]))
;; no namespace - source symbol
;; l/sym - "lowered" form - special form not directly writable from source
@ -9,52 +9,80 @@
;; lowered form
(defn left-associative [symbol]
(r/rewrite
(~symbol ?left ?right . !more ..1) (~symbol (~symbol ?left ?right) !more ...)))
(~symbol ?m ?left ?right . !more ..1) (~symbol {} (~symbol ?m ?left ?right) !more ...)))
(defn simple-identity [symbol]
(r/rewrite (~symbol ?v) ?v))
(r/rewrite (~symbol _ ?v) ?v))
(defn left-binop-desugar [symbol]
(r/choice (left-associative symbol) (simple-identity symbol)))
(defn equatable-type? [typesym] (contains? #{'f64 'i32 'bool} typesym))
(defn ordered-type? [typesym] (= typesym 'f64))
(defn logical-type? [typesym] (= typesym 'bool))
(defn numerical-type? [typesym] (= typesym 'f64))
(defn combinator-typecheck [valid?]
(r/rewrite (_ (m/pred valid? ?l) (m/pred valid? ?r)) [[?l ?l ?l] [?r ?r ?r]]))
(defn comparitor-typecheck [valid?]
(r/rewrite (_ (m/pred valid? ?l) (m/pred valid? ?r)) [['bool ?l ?l] ['bool ?r ?r]]))
(def specials
{'l/if
{:desugar (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)))
:validate (r/rewrite ('if) "if statement needs at least a condition and a body"
('if _) "if statement needs at least one body expression")}
('if ?m ?cond ?body & ?more) ('i/if ?m ?cond ?body & ?more)
('i/if ?m) ('do ?m)
('i/if _ ?else) (label ?else "else block")
('i/if ?m ?cond ?body & ?more) ('l/if ?m (label ?cond "condition")
(label ?body "body")
('i/if {} & ?more)))
:validate (r/rewrite ('if _) "if statement needs at least a condition and a body"
('if _ _) "if statement needs at least one body expression")
:typecheck (r/rewrite (_ ?cond ?l ?r) [[?l 'bool ?l ?l] [?r 'bool ?r ?r] ['void 'bool 'void 'void]])}
'l/fn
{:desugar (r/rewrite
('fn (m/pred symbol? ?name) & ?rest) (meta-split {:name ?name} ('def ('fn & ?rest)))
('fn [!names ... '-> ?return-type] & ?body) (i/fn [!names ... (label ?return-type "return type")] & ?body)
('fn [!names ...] & ?body) (i/fn [!names ... 'void] & ?body)
('i/fn [(typed !names !types) ... ?return-type] & ?body)
(meta-split {:type ['fn . !types ... ?return-type]
:params [!names ...]}
('l/fn ('do & ?body))))}
'+ {:desugar (left-binop-desugar '+)}
'-
{:desugar (r/choice
(r/rewrite ('- ?v) ('- 0 ?v))
(left-associative '-))}
'* {:desugar (left-binop-desugar '*)}
'/ {:desugar (left-binop-desugar '/)}
'= {}
'not= {}
'< {}
'<= {}
'> {}
'>= {}
'and {:desugar (left-binop-desugar 'and)}
'or {:desugar (left-binop-desugar 'or)}
'def {}
'do {}
'l/lit {}
'l/call {}})
('fn {& ?m} (m/pred symbol? ?name) & ?rest)
('def {:name ?name} ('fn {:name ?name & ?m} & ?rest))
('fn {& ?m} [(m/app (fn [params] [params (parse-type (meta params))]) [(m/pred symbol? !names) {:type !types}]) ...
(m/app to-sym '->) (m/app parse-type {:type !types})] & ?body)
('l/fn (m/app merge ?m {:type ['fn . !types ...]
:params [!names ...]})
('do {} & ?body))
('fn {& ?m} [(m/app (fn [params] [params (parse-type (meta params))]) [(m/pred symbol? !names) {:type !types}]) ...] & ?body)
('l/fn (m/app merge ?m {:type ['fn . !types ... 'void]
:params [!names ...]})
('do {} & ?body)))
:typecheck (r/rewrite ({:type ['fn . _ ... ?return-type]} _) [[?return-type ?return-type]])}
'+ {:desugar (left-binop-desugar '+)
:typecheck (combinator-typecheck numerical-type?)}
'- {:desugar (r/choice
(r/rewrite ('- ?m ?v) ('- ?m 0 ?v))
(left-associative '-))
:typecheck (combinator-typecheck numerical-type?)}
'* {:desugar (left-binop-desugar '*)
:typecheck (combinator-typecheck numerical-type?)}
'/ {:desugar (left-binop-desugar '/)
:typecheck (combinator-typecheck numerical-type?)}
'= {:typecheck (comparitor-typecheck equatable-type?)}
'not= {:typecheck (comparitor-typecheck equatable-type?)}
'< {:typecheck (comparitor-typecheck ordered-type?)}
'<= {:typecheck (comparitor-typecheck ordered-type?)}
'> {:typecheck (comparitor-typecheck ordered-type?)}
'>= {:typecheck (comparitor-typecheck ordered-type?)}
'and {:desugar (left-binop-desugar 'and)
:typecheck (comparitor-typecheck logical-type?)}
'or {:desugar (left-binop-desugar 'or)
:typecheck (comparitor-typecheck logical-type?)}
'def {:typecheck (r/rewrite (_ ?t) [[?t ?t]])}
'do {:typecheck (r/rewrite (_) [['void]]
(_ . !stmt ... ?last) [[?last . (m/app (constantly 'void) !stmt) ... ?last]])}
'l/read {:typecheck (r/rewrite ({:type ?type}) [[?type]])}
'l/lit {:typecheck (r/rewrite ({:type ?type}) [[?type]])}
'l/call
{:typecheck (r/rewrite (_ (m/and ['fn . !param-types ... ?return-type] ?fn-type) . _ ...)
[[?return-type ?fn-type . !param-types ...]])}
})

View file

@ -2,6 +2,13 @@
(:require [meander.epsilon :as m]
[tock.compiler.util :refer [bind! compile-error form-dispatch lookup map-subexpressions new-scope] :as u]))
; once we've hit the typechecking stage, nodes in our expression tree are maps, where each node has at least :special
; and :args keys. :special is a symbol corresponding to a record in the `specials` map; :args is an ordered list of
; subexpressions. a node may optionally contain a :type field as well.
; typechecking happens bottom-up. by the time a node is called to be typechecked, the system has verified that all of the children
; under :args will have a :type field.
; there are two type syntaxes:
; 1. internal syntax - always stored in a form's metadata under the :type key. fetched with `expr-type`, added by
@ -11,6 +18,7 @@
; 2. input syntax - stored in metadata with ^{} prefix syntax. converted to tock's internal syntax with `syntax-type`.
; generally, type names are stored as {:tag 'name}, and parameterized types like ['enum ...] are
; written in the input as ^{enum [...]}. (if there is only one parameter, the vector is skipped).
; The desugaring process handles this conversion.
(defn syntax-type [metadata _ctx error]
(m/match metadata