~brenns10/funlisp

ref: v1.2.0 funlisp/doc/advanced-types.rst -rw-r--r-- 2.8 KiB
8947d954Stephen Brennan Release v1.2.0 1 year, 8 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
Type System
===========

As previous pages have mentions, lisp objects rely on a mark and sweep garbage
collection system to manage their lifetimes. This page describes how the type
system is implemented, which includes the garbage collection implementation.

In Java, everything is an object. In this language, everything is a
:c:type:`lisp_value`.  This means two things:

1. Every object contains a ``type`` pointer.
2. Every object has a variable to store its ``mark``.
3. Every object has a pointer to the object allocated after it, forming a linked
   list.

Every type declares these using the ``LISP_VALUE_HEAD`` macro, like so:

.. code:: C

   typedef struct {
     LISP_VALUE_HEAD;
     int x;
   } lisp_integer;

You can cast pointers to these objects to ``lisp_value*`` and still access the
type object. All objects are passed around as ``lisp_value*``.

In order to allow objects to be treated differently based on their type (but in
a generic way to calling code), we use the type object.

A type object is just another object, with type :c:type:`lisp_type`. However, it
is NOT managed by the garbage collector. It contains the string name of a type,
along with pointers to implementations for the following functions: print, new,
free, eval, call, and expand. Therefore, if you have ``lisp_value *object`` and
you want to print it, you can do:

.. code:: C

   object->type->print(stdout, object);

Unfortunately that's very verbose. To simplify, each of the functions
implemented by a type object has an associated helper function. So you can
instead do:

.. code:: C

   lisp_print(stdout, object);

Which, under the hood, simply gets the type object and uses the print function.
But there's no magic or switch statements involved here--we're simply using the
type object.

All of the type object operations have their own helper functions like print.
The advantage to doing this, besides less verbose code, is that shared
operations can be done together. For example, the ``lisp_new()`` function does
some garbage collection related operations that none of the type object
``new()`` methods need to know about.

As a result of these type objects, it's not too difficult to add a type to the
language! All you need to do is declare a struct for your type, implement the
basic functions, and create a type object for it. The type object must implement
the following operations:

- print: writes a representation of the object to a file, without newline
- new: allocates and initializes a new instance of the object
- free: cleans up and frees an instance
- expand: creates an :doc:`iterator<advanced-iterator>` of ALL references to
  objects this object owns (see the
  :doc:`garbage collection documentation<advanced-gc>`)
- eval: evaluate this in a scope
- call: call this item in a scope with arguments