Write lisp interpreter in java (10 implementation objects and classes)

Posted by timcapulet on Sat, 19 Feb 2022 10:59:36 +0100

In the process of implementing the interpreter, I found a fun thing, that is, how to better use the object-oriented idea to write code, and think about how to define a set of templates. Before starting again, I sorted out two kinds of object-oriented templates.
One is a java style template

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

    )

    func j()(

    )
))

One is the go style template

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

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

  ))
)

Personally, I think the go style is lighter to write and takes less time to implement. Then I chose the go style.

Realization idea

There are two ways to realize it. One is to write code and analyze it through java code. Although it is simple, it is better to think about using lisp macro or lisp function. There is no need to increase the amount of java code. Moreover, it is only a syntax sugar, and it is not difficult to use macro. Then we started the coding process, and spent two hours writing a main body in the morning, In the afternoon, I spent more than two hours debugging and modifying. Now this set of code can run normally, so let's start.
Before we start, we should first determine what we want to achieve. For a complete object system, such as parent class import and export, we can't achieve it all in a short time, and then we don't need to achieve it all. In this case, we can only achieve the following goals.

Two templates:

  • Structure defined template
  • Structure method defined template

Three results:

  • Objects can be created
  • Objects can call their own methods
  • Can be called between object methods

preparation

First, we define a syntax sugar of defun with a macro

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

Then think about how to layout the data and process

We divide the object into two parts: head and body

  • head is the information of the class
  • body is the field value in the object

The head structure is represented by json as follows:

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

The following is the definition of head

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

The body is implemented with vectors. Objects with the same structure share the same head, which is exclusive to the body.

code

Two templates

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)))
)))

Two templates are done.

test

First define the structure and method

;Define structure
(define dog (struct (name age color)))
; Method of defining structure
(func (dog) hello (a b c) (
     (println (string-append 'hello: '(dog `get-name) a b c))
))
; Definition method
(func hello (a b c) (
     (println (string-append 'hello:' a b c))
))

test

<= 
(
    
   (hello  'a' 'b' 'c')
   ;Define structure
   (define dog-obj (dog 'Dog' 5 'white'))
   (dog-obj `hello (`( 'a' 'b' 'c')))
   ;Inter method call
   (dog-obj `hello  'a' 'b' 'c')
   ;Call your own method
   (println (dog-obj `get-name))
   (dog-obj `set-name 'Dog 0')
   (println (dog-obj `get-name))
   (println (dog-obj `get-name))
   (dog-obj `set-name ('Dog 0'))
)
=>
'hello:abc'
'hello: Dog abc'
'hello: Dog abc'
'Dog'
'Dog 0'
'Dog 0'

Everything was normal and the three results met expectations.

We can also encapsulate a new

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

Then we can define it like this

 (define dog-obj0 (new dog('Dog 55' 5 'white')))

Test it

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

Meet expectations.

summary

Our object system uses macro functions as its implementation. The definition structure returns a function. As above, the return result of dog structure is a function; The function returned by creating the object is also a function. In this function, we define a local variable val, which stores the value just passed in. Then the function returned by creating the object can accept two values, one is the method name and the other is the parameter; Then call the corresponding function according to the method name. If it is a get set method, we pass val, that is, the object information and parameter combination, to the function, and then we can modify or obtain the value stored in the body; If it is a custom method, we take out the function itself and parameters and combine them to realize the mutual call between object methods. Of course, if the method does not exist, an exception will be thrown.

At present, this object system is not perfect, such as visible range modifier, calling method, application of other class packages, etc
There are also many problems. For example, head and method are variables rather than one for each structure.

Topics: Java object lisp