Basic GDL Overview and Syntax ============================= This file contains basic information and usage instructions for the base General-purpose Declarative Language System. For further documentation and updates, please visit http://www.genworks.com or contact us at info@genworks.com or 248-910-0912. The GDL product is a commercially available KBE system, and the core GDL language is a proposed standard for a vendor-neutral KBE language. Core GDL Syntax ================== GDL is based on and is a superset of ANSI Common Lisp. 1 define-package ================ The macro gdl:define-package is used to set up a new working package in GDL. Example: (gdl:define-package :gdl-user) The :gdl-user package is an empty, pre-defined package for your use if you do not wish to make a new package just for scratch work. For real projects it is recommended that you make and work in your own GDL package. Notes for advanced users: Packages defined with gdl:define-package will implicitly :use the GDL package and the Common-Lisp package, so you will have access to all exported symbols in these packages without prefixing them with their package name. You may extend this behavior, by calling gdl:define-package and adding additional packages to use with (:use ...). For example, if you want to work in a package with access to GDL exported symbols, Common Lisp exported symbols, and symbols from the Surf (NURBS surfaces and brep solids) package, you could set it up as follows: (gdl:define-package :my-gdl-user (:use :gdl :surf)) 2 define-object =============== define-object is the basic macro for defining objects (i.e. creating classes) in GDL. A GDL object definition is a superset of a CLOS Standard Class. Basic syntax of Define-Object is (define-object ) is any non-keyword symbol. A CLOS Standard Class will be generated for this symbol, so any name you use will override a defclass if one is already defined with the same name. is a list of other class-names from which this object will inherit. It maps directly into the CLOS mixin list. Note that the standard mixin GDL:Vanilla-Mixin gets mixed in automatically with any GDL object and carries some of the basic GDL functionality (messages). is a plist made up of pairs made from special keywords and expression lists. The special keywords currently supported are the following, and each is documented in the respective section of this file: :input-slots, :computed-slots, :trickle-down-slots, :objects, :hidden-objects, :functions, and :methods. 2.1 :input-slots ================ :input-slots are made up of a list, each of whose elements is either a symbol or a list expression whose first element is a symbol. In either case, the symbol represents a value which can be supplied either (a) into the toplevel object of an object hierarchy, at object instantiation (see (1.5), make-object, below) (b) into a child object, using a :objects specification (see (1.6), :objects, below) Inputs are specified either as a simple symbol (which may, but need not be, a keyword symbol), or as an expression whose first is a symbol and whose second is an expression returning a value which will be the default value for the input slot. Optionally, a third item can be supplied, the keyword :defaulting, which indicates that if a slot by this name is contained in any ancestor object's list of :trickle-down-slots, the value from the ancestor will take precedence over the local default expression. Example 1: (define-object person (base-object) :input-slots (first-name last-name age image-url)) In this example, the slots first-name, last-name, age, and image-url are all defined, with no default expressions. This means that for the object to answer these messages, these slots must be specified at the time of object instantiation. Example 2: (define-object person (base-object) :input-slots (first-name last-name age (image-url "http://localhost:9000/images/"))) In this example, first-name, last-name, and age are all defined with no default expressions, but image-url has the default expression "http://localhost:9000/images/." This means that if nothing is specified for image-url at object instantiation time, the image-url message will return "http://localhost:9000/images/." Example 3: (define-object person (base-object) :input-slots (first-name last-name age (image-url "http://localhost:9000/images/" :defaulting))) This example is the same as Example 2, with the exception that if image-url is included in an ancestor object (see below for discussion of object hierarchies) as a :trickle-down-slot, the slot's value from that ancestor will take precedence over the local default expression of "http://localhost:9000/images/." 2.2 computed-slots ================== computed-slots are messages which are generally computed based on their default expression. computed-slots will only be computed when called ("demanded"), then their values will be cached in memory. Only if another slot on which they depend becomes modified will they become unbound, then their values will be recomputed from their expressions when demanded. The referencing macro ``the'' is used to refer to the values of messages within the current object (named implicitly with the variable "self"), or, through reference-chaining (see (1.6), :objects, below), the values of messages in other object instances. Notes for Advanced users: In packages created with gdl:define-package, the Common Lisp symbol ``the'' is shadowed by gdl:the. If you wish to access common-lisp:the, use the explicit package prefix, e.g. ``cl:the.'' Example 1: (define-object person (base-object) :input-slots (first-name last-name age image-url) :computed-slots ((full-name (concatenate 'string (the first-name) " " (the last-name))))) In this example, the message full-name is always computed strictly based on its default expression, which concatenates (the first-name) and (the last-name). Example 2: (define-object person (base-object) :input-slots (first-name last-name age image-url) :computed-slots ((full-name (concatenate 'string (the first-name) " " (the last-name)) :settable))) In this example, the message full-name is by default computed based on its default expression, which concatenates (the first-name) and (the last-name). However, because it is :settable, its value may be altered procedurally at runtime (see "setting slot values" below) 2.3 :objects and :hidden-objects ================================ :objects is used to specify a list of Instance specifications, where each instance is considered to be a ``child'' object of the current object :hidden-objects serves the same purpose and has the same syntax, but hidden objects are considered ``hidden-children'' rather than ``children'' (so they are not returned by a call to (the children), for example). Inputs to each object are specified as a plist of inputs and value expressions, spliced in after the objects's name and type specification: Examples ======== > (define-object city (base-object) :computed-slots ((total-water-usage (+ (the hotel water-usage) (the bank water-usage)))) :objects ((hotel :type 'hotel :size :large) (bank :type 'bank :size :medium))) --> CITY > (define-object hotel (base-object) :input-slots (size) :computed-slots ((water-usage (ecase (the size) (:small 10) (:medium 20) (:large 30))))) --> HOTEL > (define-object bank (base-object) :input-slots (size) :computed-slots ((water-usage (ecase (the size) (:small 2) (:medium 3) (:large 4))))) --> BANK > (setq self (make-object 'city)) --> # > (the total-water-usage) --> 33 The special message children will return a list of all the child instances in a object: > (the children) --> (# #) 2.4 Sequences of Objects ======================== 2.4.1 Fixed-size Sequences ========================== Objects may be specified as a fixed-length sequence, analogous to a single-dimensional array. Although we call this a fixed-length sequence, the length can change if something it depends on becomes modified. But if this happens, the entire sequence will have to be recomputed. Each member of the sequence will automatically answer an :index message, which starts at 0 goes up to one less than the total number of elements in the sequence. Note that the referencing macro ``the-child'' may be used to reference into the current child objects (in sequenced objects as well as in normal non-sequenced objects). This can be useful for sequenced objects, in order to access the :index of the current member. Example (defparameter *presidents-data* '((:name "Carter" :term 1976) (:name "Reagan" :term 1980) (:name "Clinton" :term 1990))) (define-object presidents-container (base-object) :input-slots ((data *presidents-data*)) :objects ((presidents :type 'president :sequence (:size (length (the data))) :name (getf (nth (the-child index) (the data)) :name) :term (getf (nth (the-child index) (the data)) :term)))) (define-object president (base-object) :input-slots (name term)) For convenience, the special objects keyword :Parameters may be used to pass an actual plist into a child instance instead of having to refer to the individual parameters. Example: (define-object presidents-container (base-object) :input-slots ((data *presidents-data*)) :objects ((presidents :type 'president :sequence (:size (length (the data))) :parameters (nth (the-child index) (the data))))) The members of quantified set are accessed like functions, by wrapping extra parentheses and including the index number as the argument. Example: > (setq self (make-object 'presidents-container)) --> # > (the (presidents 0) name) --> "Carter" The quantified set can handle certain pre-defined messages, including last and first. Example: > (the (presidents last)) --> # Members of a quantified set can also handle the messages previous, next, first?, and last?. The types of a quantified set can also be quantified, by supplying them as a list and using the keyword :sequence in the :type specification, e.g. (define-object stuff (base-object) :computed-slots ((child-types (list 'boy 'girl 'man 'woman))) :objects ((people :type (:sequence (the child-types)) :sequence (:size (length (the child-types)))))) If the expression returning the :sequence of types, or of the :size, of a fixed-size sequence becomes modified, or anything they depend on becomes modified, then the entire sequence will become unbound and will have to be recomputed the next time it is demanded. 2.4.2 Variable-size Sequences ============================= Objects may be specified as a variable-length sequence, analogous to a list. These are similar to fixed-length sequences, but the syntax is: :sequence (:indices ) where the is an initial list of indices. The indices are usually integers, but can be any object which matches with eql (e.g. keyword symbols). For inserting and deleting members of a variable-length sequence, please see the reference documentation on variable-sequence. 2.5 :functions ============== Functions are uncached methods on the object, which discriminate only on the type of the object. They are defined with a normal (non-specialized) lambda list, so they do not discriminate on the types of their arguments other than the implicit ``self'' argument. Functions are called in a normal reference chain but their name is wrapped in parentheses and the lambda-list is spliced on after the name, within the parentheses. Example: ======= (define-object hotel (base-object) :input-slots (room-rate) :functions ((total-cost (number-of-nights) (* (the room-rate) number-of-nights)))) > (setq self (make-object 'hotel :room-rate 100)) --> # > (the (total-cost 7)) --> 700 > (the (total-cost 10)) --> 1000 2.5 :methods ============== Methods are identical to GDL Functions, with the additional capability of specializing on their argument signature (i.e. the combination of types of the arguments) in addition to the implicit ``self'' argument (as with standard CLOS methods). 2.6 :trickle-down-slots ======================= :trickle-down-slots are a list of symbols naming other messages (:input-slots, :computed-slots, etc.) in the object which will automatically be available in any descendant (e.g. child, grandchild, etc.) instances, unless overridden in the descendant instance (e.g. by being defined as an :input-slot, :computed-slot, etc, in the descendant instance). Example: (define-object person (base-object) :input-slots (social-security-number) :trickle-down-slots (social-security-number) :objects ((irs-records :type 'irs-records) (state-tax-returns :type 'state-tax-returns) (fbi-file :type 'fbi-file) (interpol-file :type 'interpol-file))) In the above object definition, the message social-security-number will be automatically available in the instances irs-records, state-tax-returns, fbi-file, and interpol-file, unless otherwise defined in those respective objects. NOTE: :objects and :hidden-objects are automatically trickle-down. 2.7 Settable Slots ================== Settable slots are just like normal slots, but their values can be programmatically modified using the special object function :set-slot!. Any other slots depending on them (directly or indirectly) will then become unbound and be recomputed the next time they are demanded. Example: > (define-object container (base-object) :computed-slots ((name "Pristine" :settable) (full-name (string-append (the :name) " Container") :settable))) > (setq self (make-object 'container)) --> # > (the full-name) --> "Pristine Container" > (the (set-slot! name "Tainted")) --> "Tainted" > (the full-name) --> "Tainted Container" Both :computed-slots and :input-slots may be specified as :settable (this includes :input-slots which are also specified as :defaulting). 3 Make-Object ============= The basic constructor for GDL objects is ``make-object.'' This maps into a call to the Common Lisp function ``make-instance,'' with some extra operations to support the GDL machinery. Keyword symbols are used to tag input values when passed into an object in a call to make-object: Example 1: > (setq myobject (make-object 'person :first-name "Albert" :last-name "Einstein")) --> # Toplevel inputs can also be specified by applying #'make-object to a plist containing the inputs: Example 2: > (setq myobject (apply #'make-object 'person (list :first-name "Albert" :last-name "Einstein"))) --> # 4 the-object ============ You can send messages to individual object instances using the macro ``the-object:'' Example: > (the-object myobject full-name) --> "Albert Einstein" The-object takes as its first argument an expression which returns an object (i.e. instance), followed by a symbol naming a message returned by that object. The symbol is immune to Lisp package, so a keyword symbol may be used, but this is not a requirement. As we will see later, the-object actually can take any number of symbols, representing a reference chain down through an object hierarcy (see "object hierarchies" below). (The ) expands to (the-object self ), so you can conveniently bind a variable named ``self'' to the result of a make-object, then use a simple ``the'' to do referencing: Example: > (setq self (apply #'make-object 'person (list :first-name "Albert" :last-name "Einstein"))) --> # > (the full-name) --> "Albert Einstein" 5 Evaluating Slot Names at Runtime ================================== The ``evaluate'' macro can be used in cases where the message name is not known until runtime -- it is wrapped around an expression which returns a symbol naming a message. The symbol is immune to package, so it may be a keyword or non-keyword symbol. Example: ======== > (setq my-key :full-name) --> :FULL-NAME > (setq self (make-object 'container)) --> # > (the (evaluate my-key)) --> "Pristine Container" 6 Formats and Views =================== 6.1 Overview ============ The basic idea behind Formats and Views is that of providing different perspectives on an object for the purposes of output. This concept is something more than ``presentation methods'' as defined by CLIM. It is more like ``presentation objects'' which contain ``presentation methods.'' Core GDL follows the message-passing paradigm of object orientation. You have objects which have slots, sub-objects, functions, etc. These are all actually methods, or messages, ``on'' the object, i.e. the message passing paradigm. Another way to look at message passing is to think that any given method dispatches, or is specialized, only on a single argument, which is the object to which it ``belongs.'' Formats and Views extend upon this notion by allowing methods to dispatch on two arguments. The first argument is a ``Format'' object, and the second argument is the normal object just as with straight GDL. Format objects are defined with ``define-format'' and instantiated only when needed, inside the body of a ``with-format'' macro. Methods which apply to a particular object and from the perspective of a particular format are defined as :output-methods with ``define-lens'' 6.2 define-format ================= As its name implies, Define-Format is used to define new formats. GDL/GWL comes with several pre-defined formats, so it is likely that you will not need to define your own formats initially. The syntax is (Define-Format ) is any non-keyword symbol. A defclass will be generated for this symbol, so any name you use will override a defclass if one is already defined with the same name. is a list of other format-names from which this format will inherit. It maps directly into a CLOS mixin list. is a plist made up of pairs made from special keywords and expression lists. Define-format in GDL currently only supports the ``:Functions'' section keyword. 6.2.1 functions =============== Functions of a format are actual uncached methods on the format object. They are defined with a normal (non-specialized) lambda list. There is a variable ``stream'' dynamically bound within the body of these functions, to which output is expected to be written. Example: ======== (define-format base-format () :functions ((a (expression) (format stream "~a" expression)) (newline-out () (format stream "~%")))) 6.3 define-lens =============== As its name implies, the define-lens macro is used to define a ``lens'' to a object, from the perspective of a given format. A lens is a way of defining methods which apply to a object when ``viewed'' through the ``lens'' of a particular format. Therefore, views are defined (and named) according to a particular object type, and a particular format. The Syntax is: (define-lens ( ) () ) is the name of a format which must already be defined with define-format. is the name of an object type which must already be defined with define-object. is currently unused. Inheritance for define-lens in GDL currently simply follows the inheritance of the particular format and object named in the define-lens. At some point more explicit inheritance control might be added using these . is a plist made up of pairs made from special keywords and expression lists define-lens in GDL currently only supports the ``:output-functions'' section keyword. Output-functions are defined like normal :functions on an object, however, in addition to sending messages to the object with normal ``the'' referencing macro, the ``write-env'' macro may also be used to call :functions which are known to be defined for the associated format. Example: ======== (define-lens (base-format try)() :output-functions ((:summary () (write-env (a "The value is: ") (a (the value)) (newline-out) (a "The color of ``this'' is: ") (a (the this color)) (newline-out) (a "The color of ``that'' is: ") (a (the (these 0) color)) (newline-out))))) 6.4 with-format =============== The with-format macro sets up an environment for calling :functions of formats (using ``write-env'' -- see below) and :output-functions of views (using ``write-the'' and ``write-the-object''). The syntax is: (With-format ( ) ) is the name of a format which has been defined with ``define-format'' is a variable or expression which evaluates to a stream which can accept output or to a string or pathname which can be opened to accept output. can contain any normal Lisp expressions as well as the format and view reference macros ``write-env,'' ``write-the-object,'' and ``write-the'' (see below). Within , the parameter ``stream'' will be dynamically bound to the stream specified by , or to a file stream opened to stream-or-pathname, if it is a string or pathname. Because it is dynamically bound, this means any other functions or methods called within will also see the correct value of ``stream.'' 6.4.1 Write-Env =============== Write-env must be called either within the (dynamic) body of a ``with-format'' or within an :output-function of a view, and is used to invoke :functions defined on the specified format Examples: (define-lens (base-format try)() :output-functions ((summary () (write-env (a "The value is: ") (a (the value)) (newline-out) (a "The color of ``this'' is: ") (a (the this color)) (newline-out) (a "The color of ``that'' is: ") (a (the (these 0) color)) (newline-out))))) (with-format (base-writer "/tmp/try.txt") (write-env (a "This is a test"))) 6.4.2 Write-The-Object ====================== Syntax: (write-the-object ) ``write-the-object'' works in similar fashion to ``the-object'' in the sense that it handles reference chains, but the last element in the reference chain must name a :output-function defined in a relevant view. ``Write-the-object'' must be called inside the (dynamic) body of a ``with-format'' so that the effective format and stream will be known. The :output-function indicated by the last element of the reference chain will be invoked, which presumably will write some output to the specified stream. Currently the ``evaluate'' macro is not implemented in GDL to resolve the :write-method name at runtime, so this name must be given as a literal symbol in the compiled source. Example: ======== (with-writer (base-format *standard-output*) (write-the-object (make-object 'try) (summary))) 2.4.3 Write-The =============== Syntax: (write-the ) ``Write-the'' is similar to ``Write-the-object,'' but it assumes ``self'' as the object, so it is not necessary to pass the object explicitly to ``write-the'' as is necessary with ``write-the-object.'' Example: ======== (with-writer (base-format *standard-output*) (let ((self (make-object 'try))) (write-the (summary)))) For further examples and a listing of built-in formats currently shipping with GDL/GWL, please see output-formats.txt. 7 Object Amendments =================== The macro define-object-amendment can be used to extend and/or redefine both user-defined objects and built-in GDL objects. The syntax for define-object-amendment is identical to that for define-object. Any additional elements will be added to the definition, and any elements with the same names as existing elements will overwrite the existing elements currently loaded into the system. 8 Extensions and Implementations ================================ Genworks also provides a large set of built-in primitives and interfaces for our GDL product, written in the GDL language. Although Genworks currently produces the only available full-featured implementation of the GDL language specification, this core language specification also represents something of a de-facto standard for KBE languages based in ANSI Common Lisp. If new implementations emerge, we encourage them to adopt this standard as well, and communicate with Genworks regarding refinements and further extensions, so that the Industry can move toward a true vendor-neutral Standard KBE language specification.