Magicode logo
Magicode
0
4 min read

I made a macro that defines a function with a type annotation.

Example

(def add [a b] [:number :number :number]
   (+ a b))

(def 2str [obj] [:any :string] 
   tostring(obj))

(def opt [?a] [:?number :number] 
   (or ?a 1))

(def len [obj] [[:table :string] :number] 
   (if (table? table obj) 
      (length obj)
      (string.length obj)

(def add [x ...] [:number :varg :number] 
   ...)

Code

(fn _2str [obj]
  "obj: string or table"
  (if
    (= (type obj) :table) (table.concat obj " or ")
    (= (type obj) :string) obj
    (assert false "expected string or table")))

(fn def [name args types ...]
  "Example:
  (def add [a b] [:number :number :number] (+ a b))
  (def 2str [obj] [:any :string] ...)
  (def opt [?a] [:?number :number] ...)
  (def len [obj] [[:table :string] :number] ...)
  (def add [x ...] [:number :varg :number] ...)"
  (assert-compile (not (= (type name) :string)) "name expects symbol, vector, or list as first arugument" name)
  (assert-compile (= (type types) :table) "types expects table as first arugment" types)
  `(fn ,name [,(unpack args)]
     (let [rest# (λ [lst#] [(unpack lst# 2)])
           empty?# (λ [str#] (or (= str# nil) (= str# "")))
           type?# (λ [?obj# type#] (if (= type# :any) true (= (type ?obj#) type#)))]
           (fn type-eq# [?actual# expect#]
                      (match (type expect#)
                        :string (if (let [res# (string.match expect# "^?")]
                                      (or (= res# nil) (= res# "")))
                                  (type?# ?actual# expect#)
                                  (or (type?# ?actual# (string.match expect# "%w+"))
                                      (type?# ?actual# :nil)))
                        :table (or (= (type ?actual#) (. expect# 1)) (type-eq# ?actual# (rest# expect#)))
                        _# false))
       ,(icollect [i# k# (ipairs args)]
                  (if (< i# (length types))
                    (if (varg? k#)
                      (assert-compile (= :varg (. types i#)) "[type mismatch] ... expects :varg" types)
                      `(assert (type-eq# ,k# ,(. types i#))
                               (.. "argument " (tostring ,k#) "[type mismatch] must be " ,(_2str (. types i#)) " but " (type ,k#))))
                    (assert-compile false "too many arguments" args)))
       (let [ret# (do ,...)]
         (assert (type-eq# ret# ,(. types (length types))) (.. "return value must be " ,(_2str (. types (length types))) " but " (type ret#)))
         ret#))))

Feature work ?

We are not able to use the function values in the def. I want to fix it someday.

Discussion

コメントにはログインが必要です。