[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2. Grundgerüst


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.1 Aufbau des Grundgerüstes


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.1.1 Übersicht und Definition des Package :kmaxima

In einem ersten Schritt wird ein Grundgerüst aufgebaut, das nach und nach mit den Funktionalitäten eines Computeralgebrasystems aufgefüllt wird. Dieses Grundgerüst besteht aus den Funktionen cl-user::run, maxima-toplevel und maxima-toplevel-loop. Ergänzend werden die zwei Funktionen maxima-banner und bye definiert. Drei globale Variablen *maxima-quiet*, *maxima-epilog* und *maxima-version* werden eingeführt.

Die Auswertung von kMaxima-Ausdrücken, die mathematische Ausdrücke, Funktionen, Variablen und kMaxima-Programme repräsentieren, wird von einem Evaluator geleistet. In Der erste Evaluator wird eine erste Implementierung beschrieben. In den weiteren Kapiteln folgt eine Beschreibung der ersten Makros und Funktionen, die von dem Evaluator ausgewertet werden. In Implementierung der Zuweisung mset wird am Beispiel der Implementierung der Zuweisung eines Wertes an eine Variable gezeigt, wie bereits mit den wenigen vorhandenen Mitteln Funktionalitäten eines Computeralgebrasystems vollständig implementiert werden können. Zuletzt wird in Implementierung der Funktion $reset in einem weiteren Beispiel gezeigt, wie die globalen Variablen in kMaxima auf ihren Anfangswert zurückgesetzt werden können.

Alle Funktionen, Makros und Variablen dieses Kapitels sind in kmaxima.lisp enthalten. Wird die Datei kmaxima.lisp geladen können die Beispiele nachvollzogen werden.

Um das hier zu entwickelnde Programm vom Original Maxima zu unterscheiden, soll es kMaxima für "kleines" Maxima genannt werden. kMaxima definiert das Package :kmaxima.

(in-package :cl-user)
(defpackage :kmaxima
  (:nicknames :kmaxima)
  (:use :cl))

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.1.2 Die Funktion cl-user::run

Wird kMaxima in ein Lisp-System geladen, kann es mit dem Kommando (run) gestartet werden. Die Funktion run wird als externes Symbol des Package :cl-user definiert und in das Package :kmaxima importiert. run hat die Aufgabe, die Funktion maxima-toplevel zu starten. Später kann die Routine ergänzt werden, um die Lisp-Umgebung zu initialisieren. Zum Beispiel können die lokalen Pfade von Dateien oder globale Lisp-Variablen initialisiert werden.

(defun cl-user::run ()
  (in-package :kmaxima)
  (let ((input-stream *standard-input*)
        (mode nil))
    (catch 'maxima-quit-to-lisp
           (loop
            (with-simple-restart (kmaxima "Return to kMaxima top level.")
              (maxima-toplevel input-stream mode))))))
(import 'cl-user::run)

Alle Funktionen und Variablen sind im Package :kmaxima definiert, das als erstes von der Funktion run geladen wird, um diese verfügbar zu machen.

Dann werden die lokalen Variablen input-stream sowie mode definiert. Die Variable input-stream wird zu *standard-input* initialisiert. Das ist die Standardeingabe des Systems, welche im Allgemeinen die Tastatur ist. Wird von der Standardeingabe gelesen, hat die Variable mode den Wert nil. Die Funktion maxima-toplevel wird mit diesen Argumenten gestartet.

Die Funktion run definiert die Catch-Anweisung mit dem Schlüsselwort 'maxima-quit-to-lisp. Wird an irgendeiner Stelle im kMaxima-Code eine Ausnahme zum Beispiel mit dem Befehl (throw 'maxima-quit-to-lisp 0) generiert, wird die Endlosschleife beendet und Maxima kehrt zum Lisp-Prompt zurück. Der Rückgabewert ist diesem Fall 0. Mit anderen Rückgabewerten können verschiedene Situationen signalisiert werden, die zum Abbruch des Programms geführt haben.

Das Lisp-Makro with-simple-restart bewirkt, dass zum Schlüsselwort kmaxima der Eintrag "Return to kMaxima top level." in die Liste der Rückkehrmöglichkeiten des Lisp-Debuggers aufgenommen wird. Bricht die Ausführung des Programms mit einem Fehler ab und wird der Lisp-Debugger aufgerufen, erhält der Nutzer die Möglichkeit, kMaxima neu zu starten.

Beispiel: Werden alle Funktionen in kmaxima.lisp geladen, kann kMaxima mit dem Kommando (run) vom Lisp-Prompt * gestartet werden. Es wird eine Information ausgegeben und der Prompt KMAXIMA angezeigt. Mit dem Kommando (break) wird der Lisp-Debugger gestartet. Unter den Rückkehrmöglichkeiten findet sich unter Punkt 1 die Auswahl "Return to kMaxima top level.". Wird diese Option ausgewählt, wird "kMaxima restarted." ausgegeben, Maxima wird neu gestartet und der Prompt KMAXIMA angezeigt. Die Ausgaben des Lisp-Debuggers hängen von dem verwendeten Lisp ab.

* (run)
kMaxima 0.1
using Lisp SBCL 1.0.45
Distributed under the GNU Public License. See the file COPYING.
Dedicated to the memory of William Schelter.

KMAXIMA> (break)
debugger invoked on a SIMPLE-CONDITION in thread #<THREAD
                                                "initial thread" RUNNING
                                                {AA8A901}>:
  break
Type HELP for debugger help, or (SB-EXT:QUIT) to exit from SBCL.
restarts (invokable by number or by possibly-abbreviated name):
  0: [CONTINUE] Return from BREAK.
  1: [MAXIMA  ] Return to Maxima top level.
  2: [ABORT   ] Exit debugger, returning to top level.
(BREAK "break")
0] 1
kMaxima restarted.
KMAXIMA>

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.1.3 Die Funktion maxima-toplevel

Nach der Initialisierung der Lisp-Umgebung startet run die Funktion maxima-toplevel. Die Funktion maxima-toplevel ist die geeignete Stelle, um die kMaxima-Umgebung zu initialisieren. Die Funktion hat die zwei Argumente input-stream und mode. run startet maxima-toplevel mit den Werten *standard-input* und nil. Diese Werte bezeichnen die Eingabe von der Tastatur. Als erstes wechselt die Funktion maxima-toplevel zum Package :kmaxima. Dies ist bereits in der Funktion run geschehen, aber es kann sein, dass kMaxima aus einer Lisp-Umgebung zurückkehrt, in der der Nutzer das Package gewechselt hat.

An dieser Stelle werden die globalen Variablen *maxima-quiet* und *maxima-epilog* eingeführt. Hat die Variable *maxima-quiet* den Wert t wird die Ausgabe eines Banners mit der Funktion maxima-banner unterdrückt. Endet die Endlosschleife, die von der Funktion maxima-toplevel ausgeführt wird, dann wird *maxima-epilog* ausgegeben und kMaxima mit dem Funktionsaufruf bye beendet. Der Standardwert der Variablen *maxima-epilog* ist eine leere Zeichenkette "".

(defvar *maxima-quiet* nil)
(defvar *maxima-epilog* "")
(let ((maxima-started nil))
  (defun maxima-toplevel (input-stream mode)
    (in-package :kmaxima)
    (if maxima-started
        (format t "kMaxima restarted.~%")
        (progn           
          (if (not *maxima-quiet*) (maxima-banner))
          (setq maxima-started t)))
    (catch 'maxima-quit-toplevel
           (loop
             (catch 'maxima-continue
                    (maxima-toplevel-loop input-stream mode)
                    (format t *maxima-epilog*)
                    (bye)))))))

Die zu der Funktion maxima-toplevel lokale Zustandsvariable maxima-started hält fest, ob Maxima zum ersten oder zum wiederholten Male gestartet wird. Im zweiten Fall wird kein Banner, sondern die Meldung "kMaxima restarted." ausgegeben.

Die Funktion maxima-toplevel führt eine Endlosschleife aus, in der die Funktion maxima-toplevel-loop aufgerufen wird.

maxima-toplevel definiert eine Catch-Anweisung mit dem Schlüsselwort 'maxima-quit-toplevel, welche die Endlosschleife beendet. Wurde maxima-toplevel von run gestartet, wird von der Funktion run kMaxima neu gestartet. Die zweite Catch-Anweisung mit dem Schlüsselwort 'maxima-continue startet die Funktion maxima-toplevel-loop neu.

Beispiel: Die Sitzung zeigt die unterschiedlichen Aufgaben der Catch-Anweisungen. Im ersten Fall wird die Funktion maxima-toplevel-loop neu gestartet, im zweiten Fall wird kMaxima neu gestartet, im letzten Fall wird kMaxima beendet und der Lisp-Prompt * angezeigt.

KMAXIMA> (throw 'maxima-continue nil)

KMAXIMA> (throw 'maxima-quit-toplevel nil)
kMaxima restarted.

KMAXIMA> (throw 'maxima-quit-to-lisp nil)
NIL
*

maxima-toplevel ruft noch die zwei Funktionen maxima-banner und bye auf. Die Funktion maxima-banner gibt eine Information aus, wenn Maxima zum ersten Mal gestartet wird. Die aktuelle Version ist in der globalen Variablen *maxima-version* enthalten.

(defvar *maxima-version* 0.1)
(defun maxima-banner ()
  (format t "~&kMaxima ~a~%" *maxima-version*)
  (format t "using Lisp ~a ~a~%" (lisp-implementation-type)
                                 (lisp-implementation-version))
  (format t "Distributed under the GNU Public License. ~
             See the file COPYING.~%")
  (format t "Dedicated to the memory of William Schelter.~%"))

Die Funktion bye beendet nicht nur die kMaxima-Sitzung, sondern auch die Lisp-Sitzung. Die Implementierung der Funktion hängt vom Lisp-Dialekt ab. Hier wird SBCL verwendet, das mit dem Kommando (sb-ext:quit) beendet wird. Der folgende Code zeigt eine Implementierung, die 10 verschiedene Lisp-Dialekte berücksichtigt.

(defun bye ()
  #+(or cmu scl clisp) (ext:quit)
  #+sbcl               (sb-ext:quit)
  #+allegro            (excl:exit)
  #+(or mcl openmcl)   (ccl:quit)
  #+gcl                (lisp:quit)
  #+ecl                (si:quit)
  #+lispworks          (lispworks:quit))

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.1.4 Implementierung der Funktion maxima-toplevel-loop

Nachdem in der Funktion run die Lisp-Umgebung und in der Funktion maxima-toplevel die kMaxima-Umgebung initialisiert sind, wird die Funktion maxima-toplevel-loop aufgerufen. Auch diese Funktion startet eine endlose Schleife.

maxima-toplevel-loop ist eine zentrale Funktion des zu entwickelnden Computeralgebrasystems kMaxima. Die Aufgabe der Funktion ist das Lesen der Eingabe, die Auswertung der Eingabe und die Ausgabe des Ergebnisses. Später kommt noch die Vereinfachung eines mathematischen Ausdrucks hinzu. Dieser Prozess wird solange wiederholt, bis die Schleife vom Nutzer oder auf andere Weise beendet wird.

Dies ist eine erste Implementierung der Funktion maxima-toplevel-loop, die zunächst eine Lisp-read-eval-Schleife ausführt.

(defun maxima-toplevel-loop (input-stream mode)
  (declare (ignore input-stream mode))
  (loop
    (format t "~%~a> " (package-name *package*))
    (finish-output)
    (format t "~{~&~S~}" (multiple-value-list (eval (read))))))

Das Einlesen der Eingabe wird mit der Lisp-Funktion read und die Auswertung mit der Lisp-Funktion eval implementiert. Für die Ausgabe des Ergebnisses wird die Lisp-Funktion format aufgerufen. Diese drei Funktionen werden in den folgenden Kapiteln ersetzt, um mathematische Ausdrücke einzulesen, zu verarbeiten und auszugeben. Die Funktion eval wird in Der erste Evaluator durch die Funktion meval ersetzt, read durch mread in Parser und format durch mdisplay in Lineare Anzeige.

Beispiel: kMaxima wird dem Kommando (run) von der Lisp-Kommandozeile gestartet. Das kMaxima-Banner wird ausgegeben und die Eingabeaufforderung KMAXIMA angezeigt. Da eine Lisp-read-eval-Schleife implementiert ist, kann jeder Lisp-Befehl eingegeben werden. Hier ist es das Lisp-Kommando (+ 2 2). Zuletzt werden die kMaxima-Sitzung und die Lisp-Sitzung mit dem Kommando (bye) beendet.

* (run)
kMaxima 0.1
using Lisp SBCL 1.0.45
Distributed under the GNU Public License. See the file COPYING.
Dedicated to the memory of William Schelter.
KMAXIMA> (+ 2 2)
4
KMAXIMA> (bye)
dieter@dieter:~/Lisp/kMaxima/kmaxima1$ 

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.2 Der erste Evaluator


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.2.1 Syntax von kMaxima-Ausdrücken

Der kMaxima-Evaluator soll die Lisp-Funktion eval ersetzen, die von der Funktion maxima-toplevel-loop aufgerufen wird, um die Eingabe des Nutzers auszuwerten. Bevor ein erster Evaluator für die Auswertung von kMaxima-Ausdrücken implementiert wird, muss die Syntax der Ausdrücke festgelegt werden. kMaxima-Ausdrücke müssen beliebige mathematische Eingaben und kMaxima-Funktionen repräsentieren. Dies gelingt mit Hilfe der folgenden Festlegungen:

Atome

Atome sind Symbole, wie mathematische Variablen a, b, …, Zahlen wie ganze Zahlen 1, 2, … oder Gleitkommazahlen 0.5, 1.25, ….

Ausdrücke

Jeder Ausdruck wird als eine Liste dargestellt, die einen Operator op und die Argumente des Operators arg1, arg2, … enhält. Die Liste hat die interne Darstellung ((op) arg1 arg2 ...). Die Argumente arg1, arg2, ... sind Atome oder wiederum Ausdrücke, wodurch verschachtelte Listen entstehen.

Das erste Element eines Ausdrucks ist eine Liste (op) mit dem Operator op als erstes Element der Liste. Diese Darstellung hat den Vorteil, dass der Operator mit Attributen versehen werden kann, ohne dass die Implementation des Evaluators modifiziert werden muss. Ein Beispiel ist ein Ausdruck der Form ((mplus simp) $a $b). Hier zeigt das Attribut simp an, dass der mathematische Ausdruck a + b von kMaxima ausgewertet und vereinfacht ist.

Nicht alle Datentypen werden von kMaxima als Atome repräsentiert. So werden rationale Zahlen von kMaxima intern als ((rat) <num> <den>) dargestellt, wobei <num> und <den> ganze Zahlen sind, welche den Zähler und Nenner der rationalen Zahl bilden. Bei rationalen Zahlen handelt es sich daher um Ausdrücke.

Jeder Operator op repräsentiert eine kMaxima-Funktion, ein Kommando, eine Programmier-Anweisung, einen Datentyp oder eine sonstige Eingabe des Nutzers. Die folgende Tabelle zeigt Beispiele, die die Syntax demonstrieren.

Eingabe         interne Darstellung       Beschreibung
---------------------------------------------------------------------
2               2                         ganze Zahl
a               $A                        mathematisches Symbol
a + b           ((MPLUS) $A $B)           Addition von Symbolen
sin(x)          ((%SIN) $X)               Sinusfunktion
diff(sin(x),x)  (($DIFF) ((%SIN) $X) $X)  Ableitung der Sinusfunktion
quit()          (($QUIT))                 kMaxima-Kommando

Eine Besonderheit ist die Unterscheidung von Lisp-Symbolen und kMaxima-Symbolen durch das Voranstellen eines $- oder %-Zeichens. Siehe Verb- und Substantivform für weitere Ausführungen zu diesem Thema.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.2.2 Implementierung des ersten Evaluators

Die Aufgabe des Evaluators ist die Auswertung von kMaxima-Atomen oder kMaxima-Ausdrücken. Dabei bewirkt die Auswertung folgendes:

  1. Auswertung von Atomen

    Zahlen und Symbole, die keinen Wert haben, werden zu sich selbst ausgewertet. Symbole, die einen Wert haben, werden durch ihren Wert ersetzt.

  2. Auswertung von Ausdrücken

    Zuerst werden die Argumente arg1, arg2, ... eines Ausdrucks ((op) arg1 arg2 ...) ausgewertet. Dann wird der Operator op auf die Argumente angewendet. Zum Beispiel wird eine Funktion mit den ausgewerteten Argumenten aufgerufen. Die Rückgabe der Funktion ist das Ergebnis der Auswertung. Es gibt Sonderformen, die eine Auswertung der Argumente ganz oder teilweise unterdrücken.

Die folgende Funktion meval ist eine erste Implementierung des Evaluators. Zuerst werden in der cond-Anweisung Atome behandelt. Es werden drei Fälle unterschieden. Ist das Atom kein Symbol oder hat das Symbol keinen Wert wird das Atom selbst zurückgegeben. Ansonsten wird der Wert des Symbols zurückgegeben.

In der zweiten cond-Anweisung wird getestet, ob ein kMaxima-Ausdruck vorliegt. Dazu wird geprüft, ob das erste Argument des Ausdrucks form eine Liste ist. Der Operator ist dann das erste Element der Liste.

In der letzten cond-Anweisung wird angenommen, dass ein Lisp-Ausdruck vorliegt. Das Argument form wird in diesem Fall mit der Lisp-Funktion eval ausgewertet.

(defun meval (form &aux u)
  (cond 
    ((atom form)
     (cond ((not (symbolp form))
            form)
           ((not (boundp form))
            form)
           (t (symbol-value form))))
    ((consp (car form))
     (let ((op (caar form)))
       (cond
         ((mfunctionp op)
          (apply op (mevalargs (cdr form))))
         ((setq u (getprop op 'mspec))
          (apply u (cons form nil)))
         ((macro-function op)
          (eval (cons op (cdr form))))
         (t
          (cons (car form) (mevalargs (cdr form)))))))
    (t (eval form))))

Trifft der Evaluator auf einen kMaxima-Ausdruck, werden die folgenden vier Fälle unterschieden.

  1. Lisp-Funktion

    Der Operator op repräsentiert eine Lisp-Funktion. Dies wird mit der Funktion mfunctionp getestet. Diese Funktion ist eine Variation der Lisp-Funktion functionp. Die Argumente werden von der Funktion mevalargs ausgewertet, dann wird der Operator op mit der Lisp-Funktion apply auf die Argumente angewendet.

  2. Maxima-Spezialform

    Es wird geprüft, ob der Operator eine kMaxima-Spezialform repräsentiert. Dazu wird mit der Funktion getprop getestet, ob eine Funktion zum Indikator 'mspec auf der Eigenschaftsliste des Operators op existiert. kMaxima-Spezialformen sind Nutzerfunktionen, die ihre Argumente nicht auswerten. In diesem Fall wird die Lisp-Funktion apply auf die nicht ausgewerteten Argumente angewendet.

  3. Lisp-Makrofunktion

    In diesem Fall repräsentiert der Operator op eine Lisp-Makrofunktion. Die Lisp-Makrofunktion wird von der Lisp-Funktion eval ausgewertet. Lisp-Makrofunktionen ermöglichen die Definition von Funktionen, die ähnlich wie Maxima-Spezialformen ihre Argumente nicht auswerten.

  4. Allgemeiner Fall

    Kann der Operator keine der oben aufgeführten Formen zugeordnet werden, werden nur die Argumente ausgewertet. Der Ausdruck wird mit den ausgewerteten Argumenten zurückgegeben.

Die oben genannten Auswertungen der Funktion meval sind noch nicht vollständig. So fehlt zum Beispiel die Auswertung einer kMaxima-Nutzerfunktion.

Immer wenn die Argumente eines Operators ausgewertet werden müssen, wird die Funktion mevalargs aufgerufen, die die Funktion meval nacheinander mit der Lisp-Funktion mapcar auf die Argumente anwendet.

(defun mevalargs (args)
  (mapcar #'meval args))

Für den Evaluator wird eine Verallgemeinerung der Lisp-Funktion functionp benötigt. Die Lisp-Funktion funktioniert nicht für Symbole, die eine Lisp-Funktion repräsentieren. Im Unterschied zum Originalcode von Maxima wird die Neudefinition der Funktion functionp vermieden, stattdessen wird eine Funktion mit dem Namen mfunctionp definiert.

(defun mfunctionp (x)
  (cond ((symbolp x)
	 (and (not (macro-function x))
	      (fboundp x) t))
        ((functionp x))))

Die Funktion meval ruft die Funktion getprop auf, um zu prüfen, ob zum Indikator 'mspec eine Funktion auf der Lisp-Eigenschaftsliste abgelegt ist. getprop arbeitet ähnlich wie die Lisp-Funktion get, testet jedoch zuerst, ob das Argument ein Symbol ist und gibt, wenn dies nicht der Fall ist, den Wert nil zurück. Die Funktionen für das Schreiben und Lesen von Eigenschaften von der Lisp-Eigenschaftsliste werden in Setzen und Lesen der Eigenschaftsliste beschrieben.

Beispiele: Die folgenden Beispiele zeigen die verschiedenen Auswertungen, die von der Funktion meval ausgeführt werden. Um eine kMaxima-Spezialform zu definieren, wird das Makro defmspec genutzt, das im folgenden Kapitel eingeführt wird.

* (meval 'a)
A
* (setq a 123)
123
* (meval 'a)
123
* (meval '((sin) 0.5))
0.47942555
* (defmspec add (args) (+ (cadr args) (caddr args)))
#<FUNCTION (LAMBDA (ARGS)) {AB40975}>
* (meval '((add) 10 20))
30
* (defmacro show (text) (format t "Nachricht: ~A~%" text))
SHOW
* (meval '((show) "Dies ist eine Nachricht."))
Nachricht: Dies ist eine Nachricht.
NIL

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.3 Die ersten Makros und weitere Funktionen


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.3.1 Definition von Funktionen mit defmspec

Bevor mit der Implementation von Funktionalitäten fortgefahren wird, die auf den ersten Entwurf des Evaluators aufbauen, sollen zwei wichtige Makros implementiert werden.

kMaxima-Nutzerfunktionen, die ihre Argumente nicht auswerten sollen, können mit dem Makro defmspec definiert werden. Der Evaluator testet, ob ein Ausdruck eine solche Nutzerfunktion repräsentiert und wertet in diesem Fall die Argumente der Funktion nicht aus. Das Makro baut auf defun-prop auf. Das Makro defun-prop dient dazu, die Definition einer Funktion zu einem Indikator auf der Eigenschaftsliste des Symbols abzulegen. defun-prop akzeptiert als erstes Argument f eine Liste mit zwei Elementen. Das erste Element ist ein Symbol für den Namen der Funktion. Das zweite Element ist ein Indikator, der den Typ der Funktion festlegt. Im Fall der kMaxima-Spezialform defmspec ist der Typ 'mspec. Das Makro defun-prop legt die Definition der Funktion `(#'(lambda ,arg ,@body)) zum Indikator 'mspec in der Eigenschaftsliste des Symbols ab, das den Namen der Funktion bezeichnet.

(defmacro defun-prop (f arg &body body)
  `(setf (get ',(first f) ',(second f)) #'(lambda ,arg ,@body)))
(defmacro defmspec (function . rest)
  `(progn
     (defun-prop (,function mspec) ,@rest)))

Beispiel: Das folgende Beispiel zeigt die Definition einer Funktion f mit dem Argument x. Das Beispiel wird in der Lisp-Kommandozeile ausgeführt. Die Funktion wird auf der Eigenschaftsliste des Symbols f abgelegt. Mit der Lisp-Funktion apply kann die Funktion angewendet werden. Das Argument muss eine Liste sein. Dies wird hier mit dem Befehl (cons 2 nil) erreicht. Der Evaluator von Maxima ist so implementiert, dass immer dann wenn eine Funktion vom Typ mspec vorliegt, die hier gezeigte Anwendung der Funktion mit apply ausgeführt wird.

* (defmspec f (x) (* 2 x))
#<FUNCTION (LAMBDA (X)) {B1DDECD}>
* (symbol-plist 'f)
(MSPEC #<FUNCTION (LAMBDA #) {B1DDECD}>)
* (apply (get 'f 'mspec) (cons 2 nil))
4

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.3.2 Definition von Variablen mit defmvar

Als nächstes betrachten wir die Implementierung des Makros defmvar. Eine vollständige Implementierung eines Computeralgebrasystems verwendet eine Vielzahl von Optionsvariablen, die den Zustand des Systems beschreiben. Um dem Nutzer zu ermöglichen, einzelne oder alle Werte im Laufe einer Sitzung auf ihren ursprünglichen Wert zurücksetzen, werden die Standardwerte der Optionsvariablen abgespeichert.

(defvar *variable-initial-values* (make-hash-table))
(defmacro defmvar (var &rest val-and-doc)
  (cond ((> (length val-and-doc) 2)
         (setq val-and-doc (list (car val-and-doc) (second val-and-doc)))))
  `(progn
     (unless (gethash ',var *variable-initial-values*)
       (setf (gethash ',var *variable-initial-values*) ,(first val-and-doc)))
     (defvar ,var ,@val-and-doc)))

Das Makro defmvar automatisiert diese Aufgabe für den Programmierer. Jede globale Variable die mit diesem Makro definiert wird, wird zusammen mit ihrem Wert in der globalen Hash-Tabelle *variable-initial-values* abgelegt.

Beispiel: Das folgende Beispiel zeigt die Definition der Variablen $myvar mit einem Standardwert von 1.25. Mit der Lisp-Funktion gethash wird der Wert wieder ausgelesen.

* (defmvar $myvar 1.25)
$MYVAR
* (gethash '$myvar *variable-initial-values*)
1.25
T

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.3.3 Setzen und Lesen der Eigenschaftsliste

Die Lisp-Eigenschaftsliste wird von kMaxima häufig genutzt, um Eigenschaften für Lisp-Symbole abzulegen. Diese Eigenschaften können Werte und Funktionen sein. Die Eigenschaftsliste erlaubt einen Programmierstil, der objektorientiert ist, ohne dass Mechanismen gebraucht werden, wie sie in anderen Programmiersprachen notwendig sind.

Um eine einheitliche Syntax zu ermöglichen, werden für das Setzen eines Wertes die Funktion putprop und das Makro defprop definiert. Im Unterschied zur Funktion putprop wertet das Makro defprop die Argumente nicht aus. Das erlaubt eine bequemere Schreibweise, da auf das Voranstellen des Quote-Operators ' verzichtet werden kann, wenn die Argumente Symbole sind. Mit der Funktion getprop kann eine Eigenschaft zu einem Symbol gelesen werden. Im Unterschied zur Lisp-Funktion get testet die Funktion getprop zunächst, ob das erste Argument ein Symbol ist. Ist dies nicht der Fall ist die Rückgabe nil.

(defun putprop (sym val indic)
  (and (symbolp sym)
       (setf (get sym indic) val)))

(defmacro defprop (sym val indic)
  `(putprop ',sym ',val ',indic))

(defun getprop (sym indic)
  (and (symbolp sym)
       (get sym indic)))

Beispiele: Es folgen einige Beispiele für die Anwendung der Funktionen.

* (putprop 'op 0.25 'float)
0.25
* (getprop 'op 'float)
0.25
* (defprop op 25 integer)
25
* (getprop 'op 'integer)
25
* (putprop 'op 1/2 'rational)
1/2
* (symbol-plist 'op)
(RATIONAL 1/2 INTEGER 25 FLOAT 0.25)

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.4 Implementierung von Funktionen


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.4.1 Implementierung einfacher Nutzerfunktionen


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.4.1.1 Die Funktion $quit

Der Evaluator kann jede Lisp-Funktion ausführen. kMaxima-Nutzerfunktionen können daher als Lisp-Funktion implementiert werden. kMaxima-Funktionen haben ein vorangestelltes Dollarzeichen $. Als erstes Beispiel wird die Funktion $quit implementiert. $quit beendet eine kMaxima-Sitzung, aber nicht die Lisp-Sitzung. kMaxima wird mit dem Rückgabewert 0 beendet.

(defun $quit ()
  (throw 'quit-to-lisp 0))

Beispiel: Im folgenden Beispiel wird eine kMaxima-Sitzung mit dem Kommando (run) gestartet, mit dem Kommando ($quit) wird die kMaxima-Sitzung beendet und dann mit dem Kommando (run) wieder gestartet.

* (run)
kMaxima 0.1
using Lisp SBCL 1.0.45
Distributed under the GNU Public License. See the file COPYING.
Dedicated to the memory of William Schelter.
KMAXIMA> ($quit)
0
* (run)
kMaxima restarted.
KMAXIMA>

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.4.1.2 Die Funktionen $writefile und $closefile

Zwei weitere Funktionen sind $writefile und $closefile mit denen die Aufzeichnung einer kMaxima-Sitzung in eine Datei gestartet und beendet werden kann. Die Implementierung nutzt die Lisp-Funktion dribble.

(defun $writefile (filename)
  (let ((msg (dribble filename)))
    (format t "~&~A~&" msg)
    '$done))

(defun $closefile ()
  (let ((msg (dribble)))
    (format t "~&~A~&" msg))
  '$done)

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.4.2 Implementierung der Zuweisung mset

Die Zuweisung eines Wertes an ein Symbol soll in einer vollständigen Implementierung mit den Operatoren : und :: möglich sein. Das Symbol $a erhält zum Beispiel den Wert 0 mit dem Kommando a:0 oder 'a::0. Während der Operator : sein erstes Argument quotiert, ist dies für den Operator :: nicht der Fall.

Die interne Darstellung als ein kMaxima-Ausdruck hat die Form ((msetq) var val) für den Operator : und ((mset) var val) für den Operator ::.

Die Zuweisung mset wird als Lisp-Funktion implementiert. Die Implementierung enthält zusätzliche Funktionalitäten, die später in einem vollständigen Computeralgebrasystems nützlich sind.

Zunächst werden die globalen Variablen *values* und *options* definiert. Die Variable *values* ist eine Liste, die alle vom Nutzer eingeführten Variablen enthält. Die Variable *options* ist die Liste der Optionsvariablen, die mit defmvar definiert wurden und vom Nutzer einen neuen Wert erhalten haben. Weiterhin werden die kMaxima-Funktionen $values und $options definiert. Mit diesen Funktionen kann der Nutzer die aktuellen Einträge der Variablen *values* und *options* ausgeben. Die Rückgabe ist eine Kopie der internen Listen und hat das Format einer kMaxima-Liste. Eine kMaxima-Liste ist ein Ausdruck mit dem Operator mlist und den Elementen der Liste als Argumente. Der Operator erhält zusätzlich das Attribut simp, um anzuzeigen, dass der Ausdruck als ausgewertet und vereinfacht angenommen werden kann.

(defvar *values* nil)
(defvar *options* nil)

(defun $values ()
  (cons '(mlist simp) (copy-list *values*)))

(defun $options ()
  (cons '(mlist simp) (copy-list *options*)))

Weiterhin wird der Schalter $optionset definiert, der die Werte t und nil annehmen kann. Hat $optionset den Wert t, gibt kMaxima eine Information aus, wenn eine Optionsvariable vom Nutzer einen neuen Wert erhält.

(defmvar $optionset nil)

(defun mset (x y)
  (cond ((symbolp x)
         (let ((f (getprop x 'assign)))
           (if (and f (or (not (eq x y))
                          (eq f 'neverset)))
               (if (eq (funcall f x y) 'munbindp)
                   (return-from mset nil))))
         (cond ((not (boundp x))
                (push x *values*))
               ((and (not (eq x y))
                     (boundp x)
                     (not (member x *values*)))
                (if $optionset
                    (format t "assignment: assigning to option ~A~%" x))
                (push x *options*)))
         (return-from mset (setf (symbol-value x) y)))
        (t (merror "assignment: cannot assign to ~A~%" x))))

Die Zuweisung eines Wertes an ein Symbol kann kontrolliert werden. Dazu wird eine Funktion zum Indikator 'assign in die Eigenschaftsliste des Symbols abgelegt. Es werden die Funktionen neverset, booleset und shadowset definiert, die die Zuweisung kontrollieren.

(defun neverset (var val)
  (mseterror var val))

(defun booleset (x y)
  (if (not (member y '(t nil $false $true)))
      (mseterror x y)))

(defun shadowset (var val)
  (mset (get var 'shadowvar) val))

Erhält ein Symbol $a zum Beispiel die Eigenschaft neverset mit dem Befehl (defprop $a neverset assign), so kann dem Symbol $a kein Wert zugewiesen werden. Das Symbol verhält sich wie eine Konstante. Die Funktion booleset kontrolliert, ob dem Symbol ein boolescher Wert zugewiesen wird. Ist dies nicht der Fall, wird die Ausführung mit einer Fehlermeldung abgebrochen. Mit der Funktion shadowset kann einem Symbol die Eigenschaft gegeben werden, einer weiteren Variablen, die Shadow-Variable genannt wird, denselben Wert zuzuweisen.

Die Funktion merror wird aufgerufen, wenn ein Fehler auftritt, der zum Abbruch der Ausführung führt. Nach der Ausgabe einer Meldung wird ein nicht-lokaler Rücksprung zum Catch 'maxima-continue ausgeführt. Dieser Rücksprung startet die Routine maxima-toplevel-loop neu.

Das Argument message enthält den Text der Fehlermeldung, der gegebenenfalls Formatierungsbefehle für auszugebende Variablen enthält, die mit dem Argument args übergeben werden.

(defun merror (message &rest args)
  (apply #'format `(t ,message ,@args))
  (format t "~& -- an error. To debug this try: debugmode(true);~%")
  (throw 'maxima-continue 'maxima-error))

Zuletzt folgt die Definition der Funktion mseterror, die von den oben definierten Assign-Funktionen aufgerufen wird, wenn eine Zuweisung an einer Variablen nicht möglich ist. Wird die globale Variable *munbindp* an den Wert T gebunden, wird die Ausführung auch dann nicht abgebrochen, wenn die Zuweisung eines Wertes an eine Variable nicht möglich ist.

(defvar *munbindp* nil)

(defun mseterror (var val)
  (declare (special *munbindp*))
  (if *munbindp*
      'munbindp
      (merror "assignment: cannot assign ~a to ~a" val var)))

Beispiele: Immer wenn die Variable $numer einen Wert erhält, wird dieser auch der Shadow-Variablen $float zugewiesen. Diese Funktionalität ist auf gleiche Weise im Original Maxima implementiert.

* (defmvar $numer nil)
$NUMER
* (defmvar $float nil)
$FLOAT
* (defprop $numer shadowset assign)
SHADOWSET
* (defprop $numer $float shadowvar)
$FLOAT
* (mset '$numer 99)
99
* $float
99

Der Optionsvariablen $optionset können nur die booleschen Werte T oder NIL zugewiesen werden. Wird versucht eine Zahl zuzuweisen, bricht die Ausführung mit einer Fehlermeldung ab.

KMAXIMA> (defprop $optionset booleset assign)
BOOLESET
KMAXIMA> (mset '$optionset nil)
NIL
KMAXIMA> (mset '$optionset t)
T
KMAXIMA> (mset '$optionset 99)
assignment: cannot assign 99 to $OPTIONSET
 -- an error. To debug this try: debugmode(true);

Das Symbol $%pi wird als Konstante deklariert. Es kann kein Wert zugewiesen werden.

KMAXIMA> (defprop $%pi neverset assign)
NEVERSET
KMAXIMA> (mset '$%pi 1)
assignment: cannot assign 1 to $%PI
 -- an error. To debug this try: debugmode(true);

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.4.3 Implementierung der Funktion $reset

An dieser Stelle schließen wir die Implementierung einer Nutzerfunktion an, mit der die Werte von globalen Optionsvariablen auf ihren Anfangswert zurückgesetzt werden können. Die Funktion $reset ist als eine kMaxima-Spezialform mit dem Makro defmspec definiert. Dies ist notwendig, da die Argumente beim Aufrufen der Funktion nicht ausgewertet werden sollen. Im Grunde könnte darauf verzichtet werden, wenn der Nutzer beim Aufruf einer Funktion die Argumente explizit quotiert, um die Auswertung der Argumente zu verhindern. Dies wäre jedoch unbequem.

Es folgen einige Hinweise zur Implementierung der Funktion reset1. Die Funktion maybe-reset ist lokal zur Funktion reset1 definiert. Da maybe-reset eine Hilfsfunktion für reset1 ist, verstecken wir diese innerhalb der Funktion reset1. Weiterhin nutzt die ursprüngliche Version im Original Maxima Seiteneffekte, um die Liste actually-reset der Funktion maybe-reset zu modifizieren. Dies ist hier nicht der Fall. Die Funktion maybe-reset hat den Rückgabewert nil, wenn der Wert key nicht zurückgesetzt wurde und ansonsten den Wert key. Die Akkumulation der Liste actually-reset geschieht nicht durch einen Seiteneffekt in der Routine maybe-reset, sondern in der Routine reset1 selbst.

Wir fügen an dieser Stelle noch die zwei kMaxima-Spezialformen msetq und mquote ein. Die Zuweisung ist mit der Lisp-Funktion mset implementiert. Diese Funktion wertet die Argumente aus. Die kMaxima-Spezialform msetq ruft die Funktion mset auf, wobei das erste Argument nicht, aber das zweite Argument mit einem Aufruf von meval ausgewertet wird.

(defmspec msetq (l)
  (mset (cadr l) (meval (caddr l))))

Beispiel: Das Beispiel zeigt den Unterschied der Zuweisung eines Wertes für die Lisp-Funktion mset und die kMaxima-Spezialform msetq.

* (setq a 'b)
B
* (meval '((mset) a 123))
123
* a
B
* b
123
* (meval '((msetq) a 999))
999
* a
999
* b
123

Das Makro mquote repräsentiert den Quote-Operator in einem kMaxima-Ausdruck. Die Auswertung eines Arguments a kann damit zum Beispiel wie in dem folgendem kMaxima-Ausdruck '((mquote) a) verhindert werden.

(defmspec mquote (form)
  (cadr form))

Zuletzt werden die Funktionen $reset und reset1 implementiert. Da eine kMaxima-Spezialform auf der Eigenschaftsliste abgelegt wird, soll die Definition der Nutzer-Funktion $reset so kurz als möglich sein. Wir führen hier die Konvention ein, dass die eigentliche Funktion eine Lisp-Funktion mit dem Index 1 ein.

(defmspec $reset (l)
  (reset1 (cdr l)))

(defun reset1 (args)
  (declare (special *variable-initial-values*))
  (labels ((maybe-reset (key val)
             (let ((reset nil))
               (when (and (boundp key)
                          (not (equalp (symbol-value key) val)))
                 (setq reset key)
                 (let ((*mundbindp* t))
                   (declare (special *munbindp*))
                   (meval `((msetq) ,key ((mquote) ,val)))))
               reset)))
    (let ((actually-reset nil))
      (if args
        (mapcar
          #'(lambda (key)
              (multiple-value-bind (val found-p)
                  (gethash key *variable-initial-values*)
                (if found-p
                    (if (maybe-reset key val)
                        (push key actually-reset)))))
          args)
        (maphash
          #'(lambda (key val)
              (if (maybe-reset key val)
                  (push key actually-reset)))
          *variable-initial-values*))
      (cons '(mlist) (nreverse actually-reset)))))

In der Funktion maybe-reset wird die Gleichheit von zwei Strukturen mit der Aussagefunktion equalp getestet. Dies muss später verallgemeinert werden, da kMaxima-Ausdrücke in ihrer Listenstruktur verschiedene weitere Informationen enthalten können und sich dadurch voneinander unterscheiden, obwohl die Ausdrücke äquivalent sind.

Beispiel: In diesem Beispiel definieren wir die Optionsvariable $option und geben ihr den Wert 1. So dann ändern wir den Wert auf 99 und rufen dann die Funktion reset1 auf, um die Variable auf ihren ursprünglichen Wert zurückzusetzen. Die Rückgabe der Funktion reset ist eine kMaxima-Liste, die die Variablen enthält, die zurückgesetzt wurden. In diesem Fall haben wir nur die Variable $option zurückgesetzt.

* (defmvar $option 1)
$OPTION
* (setq $option 99)
99
* (reset1 '($option))
((MLIST) $OPTION)
* $option
1

Alle Funktionen, Variablen und Makros die in Grundgerüst definiert wurden können mit der Datei kmaxima.lisp in ein Lisp-System geladen werden.


[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

This document was generated by Dieter Kaiser on Dezember, 13 2011 using texi2html 1.76.