用java寫lisp 直譯器 (10 實現物件和類)

yangrd發表於2022-02-19

在實現直譯器的過程中,發現了一個好玩的東西,那就是怎樣更好的使用物件導向的思路來編寫程式碼, 想了想可以定義一套模板。再開始前先整理了兩種物件導向的模板。
一種是java風格的模板

(class classname (superclass) (. field) (
    func t()(

    )

    func j()(

    )
))

一種是go風格的模板

(
  (define struct-name (struct (. field)))
  (func (struct-name) hello () (

  ))
  (func (struct-name) j () (

  ))
)

個人感覺還是go風格的編寫起來更輕一些,實現起來花費時間也短,然後就選擇了go風格的。

實現思路

實現思路有兩種一種是通過java程式碼去編寫程式碼去解析它,雖然簡單,但想了想用lisp的巨集或lisp函式其實會更好一些,不用在去增加java的程式碼量,而且本身不過是一種語法糖,用巨集也並不難,然後就開始了編碼過程,上午花了兩個小時寫了一個主體,下午又花了兩個多小時除錯修改,現在這套程式碼可以正常執行了,那麼就讓我們開始吧。
在開始前先確定要實現成什麼樣子,完整的物件系統如 類 父類 匯入 匯出等,我們不可能短時間全部實現,然後也沒必要全部實現,既然這樣那就只實現如下這些目標。

兩個模板:

  • 結構定義的模板
  • 結構的方法定義的模板

三個結果:

  • 物件可以建立
  • 物件可以呼叫自己方法
  • 物件方法之間可以呼叫

準備工作

首先我們先用巨集定義一個 defun 的語法糖

(define-macro defun (lambda (name args . body) (
    `(
        define ,name (lambda ,args ,@body)
    )
)) )

然後思考怎樣對資料和過程佈局

我們將 物件 分成head 和 body 兩部分

  • head是類的資訊
  • body 是物件裡面的欄位值

head結構是這樣用json表示:

{
    id: {
        type: struct,
        name: struct-name
    },
    info:{
        fieldnames:[...fields],
        methods:{
            method-name: {
                type: self/func,
                ref: method
            }
        }
    }
}

下面是head 的定義

(
    (define methods (make-dict))
    (define struct-head 
        (cons (cons `struct `struct-name) 
        (cons `field-names-vector methods)))
)

body用向量實現,同一個個結構的物件共享同一個head,body自己獨享。

編碼

兩個模板

1.struct

(define-macro struct (lambda (. fields)
 //(set-cdr! (car struct-head) (hack-struct-name fields))
 (set-car! (cdr struct-head) (list->vector fields))
 (map (lambda (f index)
        (dict-put! methods (string->symbol (string-append 'get-' (symbol->string f))) (cons `self (lambda (o) (vector-ref (cdr o) index))))
        (dict-put! methods (string->symbol (string-append 'set-' (symbol->string f))) (cons `self (lambda (o v) (vector-set! (cdr  o) index v))))
       )
      fields)
 (lambda (. field-values)
    (define val  (cons struct-head (list->vector field-values)))
    (define self (lambda (key . msg) (
        (define method-info (dict-get methods key))
        (if (pair? method-info)
            (
                (define method (cdr method-info))
                (define args nil)
                (if  (eqv? `self (car method-info))
                     (
                         (set! args (list val))
                         (list-add-all args msg)
                     )
                     (
                        (set! args (list self))
                        (list-add-all args msg)
                     )
                )
                (apply method args)
            )
            (error (string-append 'not method  ' (symbol->string key)))
        )
    )))
    self)
))

2.func

(define-macro func (lambda (. data) (
    if (exp? (car data))
    (
        (define v (list->vector data))
        (define struct-name (car (vector-ref v 0)))
        (define func-name (vector-ref v 1))
        (define func-params (vector-ref v 2))
        (define func-body (cdr (cdr (cdr data))))

        (define temp-func-params (list struct-name))
        (map (lambda (param) (list-add temp-func-params param))  func-params)
        (set! func-params temp-func-params)
        (dict-put! methods func-name
         (cons `func (apply (`(lambda ,func-params ,@func-body))))
        )
    )
   (`(defun ,@data)))
)))

兩個模板搞定。

測試

先定義結構和方法

;定義結構體
(define dog (struct (name age color)))
; 定義結構體的方法
(func (dog) hello (a b c) (
     (println (string-append 'hello: ' (dog `get-name) a b c))
))
; 定義方法
(func hello (a b c) (
     (println (string-append 'hello:' a b c))
))

測試

<= 
(
    
   (hello  'a' 'b' 'c')
   ;建立例項
   (define dog-obj (dog '狗子' 5 '白色'))
   (dog-obj `hello (`( 'a' 'b' 'c')))
   ;方法間呼叫
   (dog-obj `hello  'a' 'b' 'c')
   ;呼叫自己的方法
   (println (dog-obj `get-name))
   (dog-obj `set-name '狗子0')
   (println (dog-obj `get-name))
   (println (dog-obj `get-name))
   (dog-obj `set-name ('狗子0'))
)
=>
'hello:abc'
'hello: 狗子abc'
'hello: 狗子abc'
'狗子'
'狗子0'
'狗子0'

一切正常,三個結果符合預期。

我們還可再封裝一個new

(define-macro new (lambda (f . vs)
 ((apply f) vs)
))

然後我們就可以這樣定義了

 (define dog-obj0 (new dog('狗子55' 5 '白色')))

測試一下

<= (println (dog-obj0 `get-name))
=> '狗子55'

符合預期。

總結

我們的物件系統使用了巨集函式作為實現,定義結構返回的是一個函式 如上 dog 結構體返回結果是個函式; 建立物件返回的也是一個函式,這個函式裡面我們定義了一個區域性變數val 裡面儲存了剛才傳入進來的值,然後建立物件返回的這個函式可以接受兩個值, 一個是方法名,一個是引數; 然後根據方法名呼叫對應的函式 如果是get-set 方法我們將 val 也就是物件資訊和引數組合 傳給函式 然後就可以對body 裡面儲存的值進行修改或獲取;如果是自定義的方法 ,我們把這個函式本身和引數取出進行組合就實現了物件方法之間的可以互相呼叫,當然如果方法不存在的話就丟擲異常。

目前這個物件系統很不完善,如 可見範圍 修飾符 呼叫方式 應用其他類 包等
也存在諸多問題,如head 和 method 是一個變數 而非每一個結構體都生成一個。

相關文章