在实现解释器的过程中,发现了一个好玩的东西,那就是怎样更好的使用面向对象的思路来编写代码, 想了想可以定义一套模板。再开始前先整理了两种面向对象的模板。
一种是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 是一个变量 而非每一个结构体都生成一个。


yangrd
1.3k 声望225 粉丝

代码改变世界,知行合一。