BRENDA Most of this material is explained in detail in ps6, so we'll just be sketchy here. Overview of the Brenda Interpreter ---------------------------------- Brenda is contained in 8 files: * load.dyl - Loads all the files. Contains a debugging bit that you can set to suppress loading messages. * utilities.dyl - Repository for general utility functions. Right now only andn and orn. * classes.dyl - defines all the brenda classes. Look here to see the basic definitions of brenda-garbage (for garbage collection), class ordering, environments (frames, bindings, etc.) and various types of brenda-objects. The code to add a method to a generic function is also found here. Q: Why don't we define make-brenda-class earlier and use it to define brenda-object-class, brenda-type-class, and brenda-class-class? A: The references are circular: brenda-class-class is an instance of itself. We want make-brenda-class to stick brenda-class-class in the class slot of everything it creates, but we can't do this until brenda-class-class is defined. * frames.dyl - environment manipulation functions. Create frames, add bindings, lookup. Q: Why do frames have a pointer to the next frame? Why are are they not implemented as a list of frames? A: We want all storage to be brenda-garbage so it can be reclaimed. * garbage.dyl - garbage collection code (mark+sweep). * brenda.dyl - the heart of the interpreter. Contains the main read/eval/print loop, code for EVAL and APPLY, generic function dispatch, parsing inputs and printing results. * primitives.dyl - definitions of system-defined brenda functions such as arithmetic operators, list, head, tail, pair, etc. * special-forms.dyl - definitions of brenda special forms such as define, cond, method, set!, quote, if, bind, etc. read ---- Many things happen in the (read) function. A sequence of input characters is transformed into a complex data structure. The Dylan (read) does some of the low-level parsing for us. It handles the tokenization of the input, identifying numbers, strings, characters, etc. It converts parenthesized expressions to Dylan s. This is nice because it saves us some boring low-level work. For example, (read) converts the input sequence (x 1 2 (y 3)) to a Dylan containing 4 elements: a x, s 1 and 2, and a Dylan , respectively. All this is done even before Brenda gets its hands on it. brenda-parse ------------ The brenda-parse function then continues the job of Dylan's read in preprocessing the input. It converts the tokenized input into a corresponding Brenda object. A Brenda object is the interpreter's internal representation of a Dylan object. For example, in this phase, Dylan s are converted to s, Dylan s are converted to s, etc. The function brenda-parse is generic and has a method for each possible type. The general case is the error case (good programming practice); almost all cases are trivial, except parsing lists; if something is already parsed (i.d. is a ) it will be left unchanged. The code for brenda-parse can be found in brenda.dyl. Type hierarchy -------------- The type hierarchy of Brenda reflects the type hierarchy of Dylan. You will understand the interaction between these types more as we go on with the analysis of the code. The type hierarchy is defined in classes.dyl. In Dylan, the value of (object-class x) is the most specific class of which x is an instance. For example, (object-class 3) ==> Thus types are objects just like any other object, and as such they have a class. Their class is . (object-class ) ==> In fact is an instance of itself: (object-class ) ==> The definition of the corresponding classes in Brenda reflects this. Code is in classes.dyl. garbage ------- is a superclass of all Brenda objects and all constructs used by the Brenda interpreter that require storage. This is done for the purposes of automatic storage management (garbage collection) and is explained fully in ps6. The allocate function simulates the allocation of memory for these constructs. The *heap* is the "memory" of our abstract computer. It is really just a list of objects that have been created. Every such object is placed on the *heap* and it will stay there till it is removed by the garbage collection algorithm. There will be a homework exercise on garbage collection. generic functions ----------------- Ozan will talk about this next week. Environments, frames, lookup ---------------------------- This works much as the environment model says it does. The code is in frames.dyl. Eval ---- Talked about this last time. We apply the same rules thay they were given when talking about the environment model. The procedure brenda-eval is generic; most cases trivial except for lists, in which case we evaluate the head and call apply. Code is in brenda.dyl. Eval-sequence ------------- Useful because some constructs allow a sequence of expressions--evaluate all of them, return the value of the last (begin, method body, bind). Weird feature: it returns #f if is given an empty sequence. We could modify the definition of the language by specifying another value if we liked. Apply ----- Again generic, with separate methods to handle primitives, user-methods, special forms, generic functions. Code is in brenda.dyl. Args are passed to brenda-apply unevaluated, because the applied function might be a special form. If not, the args are evaluated by brenda-apply. The environment has to be passed in to brenda-apply so that the args can be evaluated in the correct environment. -- primitive: eval args, check that types match, apply the function in the calls slot -- special form: do not eval args, apply the function in the calls slot -- user method: eval args, check that types match, eval the body in the env of the closure -- generic form: eval args, check types, dispatch most specific method (methods must be primitives or user methods)