[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
2.1 Aufbau des Grundgerüstes | ||
2.2 Der erste Evaluator | ||
2.3 Die ersten Makros und weitere Funktionen | ||
2.4 Implementierung von Funktionen |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
2.1.1 Übersicht und Definition des Package :kmaxima | ||
2.1.2 Die Funktion cl-user::run | ||
2.1.3 Die Funktion maxima-toplevel | ||
2.1.4 Implementierung der Funktion maxima-toplevel-loop |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
: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] | [ ? ] |
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] | [ ? ] |
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] | [ ? ] |
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.1 Syntax von kMaxima-Ausdrücken | ||
2.2.2 Implementierung des ersten Evaluators |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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 sind Symbole, wie mathematische Variablen a, b, …, Zahlen wie ganze Zahlen 1, 2, … oder Gleitkommazahlen 0.5, 1.25, ….
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] | [ ? ] |
Die Aufgabe des Evaluators ist die Auswertung von kMaxima-Atomen oder kMaxima-Ausdrücken. Dabei bewirkt die Auswertung folgendes:
Zahlen und Symbole, die keinen Wert haben, werden zu sich selbst ausgewertet. Symbole, die einen Wert haben, werden durch ihren Wert ersetzt.
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.
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.
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.
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.
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.1 Definition von Funktionen mit defmspec | ||
2.3.2 Definition von Variablen mit defmvar | ||
2.3.3 Setzen und Lesen der Eigenschaftsliste |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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] | [ ? ] |
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] | [ ? ] |
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.1 Implementierung einfacher Nutzerfunktionen | ||
2.4.2 Implementierung der Zuweisung mset | ||
2.4.3 Implementierung der Funktion $reset |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
2.4.1.1 Die Funktion $quit | ||
2.4.1.2 Die Funktionen $writefile und $closefile |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
$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] | [ ? ] |
$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] | [ ? ] |
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] | [ ? ] |
$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.