Darüberhinaus haben Perl-Programme häufig einen besonderen ästhetischen Reiz. Es ist möglich, sehr schöne und zugleich kurze Perl-Programme zu entwickeln. Larry Wall, der Erfinder von Perl, sagt es so: "Auf jeder Ebene hilft Perl Ihnen mit minimalem Getöse und maximalem Vergnügen auf Ihrem Weg von hier nach dort. Das ist der Grund, warum viele Perl-Programmierer mit einem Grinsen durch die Gegend laufen."
Hier ein kleines Beispiel für Kenner und Geniesser: Wenn Sie einen Perl-Interpreter besitzen, können Sie mit dem folgenden Einzeiler testen, ob eine Zahl prim ist, zum Beispiel die Zahl 19. Der Algorithmus (ich habe ihn im Internet gefunden) ist zwar nicht besonders effizient, aber kurz und schön:
perl -le 'print "prim" if (1 x shift) !~ /^(11+)\1+$/' 19
Die grosse Stärke der Sprache Perl liegt in der Extraktion von Daten. Das ist auch für Astrologen nützlich. Wenn Sie aus einer Datei oder aus allen Dateien eines Ordners Geburtsdaten extrahieren wollen, um irgendeine astrologische Rechnung auszuführen und die Ergebnisse in einer Statistikdatei zu sammeln, ist Perl das ideale Hilfsmittel.
Perl ist wie Java eine Interpretersprache. Sie benötigen also einen Perl-Interpreter, um Ihre Perl-Programme auszuführen. Ein bekannter und beliebter Perl-Interpreter für Windows wurde von ActiveState entwickelt und kann von deren Webseite kostenlos heruntergeladen werden. Ich verwende auf meinem Notebook das Perl-Release 5.8 mit dem Perl-Interpreter von ActiveState.
Die astrologischen Berechnungen von Häusern und Planeten liegen in der Swiss Ephemeris gekapselt vor. Für Windows-Rechner bedeutet das, Sie haben die DLL swedll32.dll auf Ihrem Rechner, und zwar in einem der Verzeichnisse, die in der Umgebungsvariablen $PATH aufgeführt sind (das sind die Verzeichnisse, die Windows auf der Suche nach Biblotheken heranzieht). Wie können Sie nun in Perl auf die Funktionen der DLL zugreifen?
Ein Beispielaufruf soll das Prinzip verdeutlichen. Wir schreiben heute Sonntag, den 16.3.2008. Das ist der Tag 2454541.5 in der fortlaufenden "julianischen" Tageszählung, wie sie die Astronomen verwenden. Der Zweizeiler
$DayOfWeek = new Win32::API('swedll32','_swe_day_of_week@8','D','I');
print $DayOfWeek->Call( 2454541.5 );
gibt die die Wochentagsnummer 6 (= Sonntag) für dieses Julianische Datum aus, wobei - und darum geht es in diesem Beispiel - die Bibliotheksfunktion swe_day_of_week() der Swiss Ephemeris zur Berechnung durchlaufen wurde.
Wie funktioniert das? Im Konstruktor der API-Calls gibt man als erstes Argument die DLL swedll32 und als zweites Argument den Namen der aufzurufenden Funktion an. Hier wurde der sogenannte "dekorierte" Name verwendet, der die Grösse des Stacks für die Parameter in Bytes enthält. (Der Grund, dass ich hier den dekorierten Namen verwende, ist schlicht der, dass es mit dem gewöhnlichen Namen nicht funktioniert.) Die folgenden beiden Argumente dienen dazu, die Signatur der gerufenen Methode zu kennzeichnen. In diesem Fall nimmt die Funktion swe_day_of_week() einen double entgegen und gibt einen Integerwert zurück. Das dritte Argument ist daher 'D' (ein Double), und das vierte Argument 'I', um den Typ des Rückgabewerts zu bezeichnen.
Einfacher ist ein DLL-Aufruf wohl nicht möglich. Zu beachten ist jedoch, dass die Implementierung den Aufrufkontext für die DLL anhand der Signatur ad hoc aufbaut. Das ist ineffizient. Um einen schnelleren Aufruf zu erhalten, ist es besser mit xsubs (externen Subroutinen), zu arbeiten, denen wir uns im nächsten Abschnitt zuwenden wollen.
Das Modul Win32::API kann von der Webseite http://dada.perl.it/ oder vom CPAN heruntergeladen werden.
Die externe Subroutine ist Teil eines gewöhnlichen Moduls, der vom Aufrufer mittels use in sein eigenes Programm eingebunden wird. Der Aufruf der externen Funktionen ist jedoch nicht direkt möglich: Die Datenstrukturen der Funktionsschnittstelle müssen an die in Perl verwendeten Datentypen angepasst werden. Für dieses Mapping wird eine eigene Syntax benutzt, die sich xs nennt. Mit dem Dienstprogramm h2xs, das Teil jeder Perl-Distribution ist, lässt sich ein Modul-Template generieren, das neben einer noch leeren xs-Datei auch ein Makefile enthält (genauer: einen "Make-Maker", ein Perl-Programm makefile.pl, dessen Ausführung ein Makefile für C-Compiler generiert). Diese leere xs-Datei gilt es mit dem Mapping der Schnittstellen zu füllen.
Eine xs-Datei ähnelt einer C-Header-Datei: Sie deklariert die zur Verfügung gestellten externen Funktionen. Neben der reinen Schnittstellendefinition kann der Aufruf im Detail festgelegt werden: Der Weg von den übergebenen Perl-Argumenten bis zum Aufruf der Funktion und danach zur Rückgabe von Perl-Daten an den Aufrufer. Eine xs-Datei kann neben xs-Sprachelementen auch C-Code enthalten.
Zur Illustration folgt hier eine einfache xs-Deklaration: Die Definition der Funktion sidtime zur Berechnung der Sternzeit. Sie hat nur ein Argument vom Typ double - das Julianische Datum - und gibt genau einen Wert zurück: Die Sternzeit.
double sidtime(tjd_ut) double tjd_ut CODE: RETVAL = swe_sidtime(tjd_ut); OUTPUT: RETVALWenn man eine xs-Definition wie diese mit dem dafür vorgesehenen sogenannten Makefile verarbeitet - zum Beispiel unter Verwendung des Programms nmake, das mit dem Microsoft Visual C++ Compiler ausgeliefert wird - so wird daraus folgender C-Code generiert:
XS(XS_SwissEph_sidtime); /* prototype to pass -Wmissing-prototypes */
XS(XS_SwissEph_sidtime)
{
dXSARGS;
if (items != 1)
Perl_croak(aTHX_ "Usage: SwissEph::sidtime(tjd_ut)");
{
double tjd_ut = (double)SvNV(ST(0));
double RETVAL;
dXSTARG;
#line 76 "SwissEph.xs"
RETVAL = swe_sidtime(tjd_ut);
#line 124 "SwissEph.c"
XSprePUSH; PUSHn((double)RETVAL);
}
XSRETURN(1);
}
Ohne dies im Detail verstehen zu müssen, können Sie dennoch nachvollziehen, was hier passiert: Nach einer Prüfung, ob die richtige Anzahl von Aufrufparametern übergeben wurde, wird der im Aufrufstack übergebene Perl-Skalar
ST(0) mittels der Funktion SvNV() in einen double gewandelt. Dann erfolgt der Aufruf der externen Funktion, hier swe_sidtime(), und schliesslich wird der reservierte Parameter RETVAL zur Rückgabe des Ergebnisses verwendet. In diesem Fall enthält die Funktion PUSHn() die Logik zur Rückkonvertierung des double-Wertes in einen Perl-Skalar.
Im Endeffekt produziert nmake nicht nur diesen C-Zwischencode, sondern auch eine (kleine) DLL, die als Mittlerin zwischen der externen Bibliothek - hier der swedll32.dll - und der Perl-Laufzeit fungiert.
Das Modul SwissEph können Sie wie folgt in Ihre ActivePerl-Installation einbinden:
Alle Ressourcen für das SwissEph-Modul: SwissEph.zip
perl makefile.pl nmake nmake test nmake install
Auf anderen Plattformen können Sie im Prinzip genauso verfahren. Jedoch werden Sie sich zuerst damit beschäftigen müssen, wie Sie die in der Datei swedll32.h notierten Funktionsdeklarationen auf Ihrer Plattform (für Ihren C-Compiler) als externe C-Bibliotheksfunktionen beschreiben.
Die Modul-Dokumentation SwissEph.html im HTML-Format zeigt einige Verwendungen.
use SwissEph; # Aufruf 1: JD = SwissEph::julday(Jahr, Monat, Tag, Weltzeit, Flag "Gregorianisch"); print SwissEph::julday(1582,1,1,0.,0)."\n"; # Aufruf 2: JD = SwissEph::julday(Jahr, Monat, Tag, Weltzeit); print SwissEph::julday(1582,1,1,0.)."\n";Warum liefern diese beiden Aufrufe dasselbe Ergebnis? Weil beim ersten Aufruf explizit der julianische Kalender gewünscht wird (an das Flag "Gregorianisch" wird der Wert 0 übergeben), während beim zweiten Aufruf ohne Kalenderflag anhand des Datums der julianische Kalender bevorzugt wird (denn der 1.1.1582 liegt vor dem 15.10.1582, ab welchem Datum erst der Vorschlagswert auf den gregorianischen Kalender wechselt).
Um in der xs-Syntax einen optionalen Parameter zu kennzeichnen, verwendet man drei Punkte ... am Ende der Deklaration. Im CODE-Block kann man dann C-Code schreiben, um den Parameter gegebenenfalls dem Aufrufstack zu entnehmen oder alternativ mit einem Vorschlagswert zu versorgen.
double
julday(year,month,day,hour,...)
int year
int month
int day
double hour
CODE:
int gregflag;
if (items > 4) gregflag = (int)SvIV(ST(4));
else gregflag = (day/370+month/12+year > 1582.87117) ? 1 : 0;
RETVAL = swe_julday(year,month,day,hour,gregflag);
OUTPUT:
RETVAL
Im Codeblock wird die Variable items herangezogen, um zu entscheiden, ob der optionale Parameter übergeben wurde (dann ist items = 5) oder nicht (dann ist items = 4). Das Macro ST(4) liefert eine Referenz auf den fünften Aufrufparemeter. Das Macro SvIV() liefert den Inhalt, auf den diese Referenz zeigt - also den Perl-Skalar - als Integerwert.
Alternativ, beim Aufruf mit nur vier Parametern, wird das gregflag anhand des übergebenen Datums vorgeschlagen. Nun kann die externe Bibliotheksfunktion swe_julday der Swiss Ephemeris aufgerufen werden, und zwar immer mit allen fünf Parametern. Das Ergebnis kommt in die reservierte Variable RETVAL, die gemäss OUTPUT-Block an den Aufrufer zurückgegeben wird.
Das xs-Mapping für die umgekehrte Funktion revjul sollte nun nicht mehr für Überraschung sorgen:
SV * revjul(tjd,...) double tjd CODE: int year, month, day; int gregflag; double hour; char date[30]; if (items > 1) gregflag = (int)SvIV(ST(1)); else gregflag = ( tjd > 2299170.5 ) ? 1 : 0 swe_revjul( tjd, gregflag, &year, &month, &day, &hour); sprintf(date,"%d.%d.%d %-.6f",day,month,year,hour); RETVAL = newSVpv(date,strlen(date)); OUTPUT: RETVALNeu ist hier lediglich, dass die Rückgabevariable ein String ist, der als neuer Perl-Skalar im CODE-Block generiert wird - hierzu dient die Funktion newSVpv(const char*, STRLEN). Da Perl so leichtfüssig auf der Stringebene operiert, erschien es mir hier angemessen, das gesamte Datum mitsamt Zeit in einen String zu schreiben und diesen zurückzugeben. Die Alternative wäre ein Array mit vier Elementen gewesen, eines davon ein double. Das hätte den Aufruf dieser einfachen Funktion unnötig verkompliziert.
Während die Signatur von swe_deltat() (ein double-Wert als Input, ein double als Rückgabewert) keine neuen Hürden bereithält, muss man sich bei swe_calc() klarmachen, wie die Übergabe von Perl-Arrays an C-Arrays funktioniert und wie man mit einer Funktion vorgeht, die nicht nur einen impliziten Rückgabewert hat, sondern auch in der Parameterliste Rückgabewerte aufweist (hier neben dem eigentlichen Rückgabewert int iret den Array xx[] mit den Planetenpositionen und den char *serr mit der allfälligen Fehler- oder Informationsmeldung).
int
calc( tjd_et, ipl, iflag, xx, serr )
double tjd_et
int ipl
int iflag
SV* xx
SV* serr
CODE:
int i, iret;
double __xx[6];
char _serr[255];
AV * _xx;
_xx = (AV*) SvRV(xx);
if (_xx != NULL) {
iret = swe_calc( tjd_et, ipl, iflag, __xx, _serr);
for (i=0;i<6;i++) av_push(_xx,(newSVnv(__xx[i])));
sv_setpv(serr, _serr);
}
else {
iret = -1;
sv_setpv(serr,"Bitte gültige Arrayreferenz in Argument 4 übergeben");
}
RETVAL = iret;
OUTPUT:
RETVAL
xx
serr
Dem Code entnehmen Sie die Antworten auf die oben gestellten Fragen:
Ein Beispielaufruf gestaltet sich folgendermassen:
my $xx = []; my $serr = ""; my $iret = 0; $iret = SwissEph::calc( 2451544.5, 0, 256, $xx, $serr);Wichtig ist die Initialisierung des Skalars $xx mit der Referenz auf einen Perl-Array (deshalb wird mit $xx und [] gearbeitet, nicht mit @xx und ()).
Nachdem es nun klar ist, wie Arrays an Perl übergeben werden, ist es leicht, auch die Funktion swe_houses() der Swiss Ephemeris auf eine Perl-Subroutine abzubilden. Da das xs hier nichts Neues bietet, zeige ich nur einen Beispielaufruf:
# Häuserspitzen
my ($iret,@cusps,@ascmc);
$iret = SwissEph::houses( 2451544.5, 52., 7., 0, \@cusps, \@ascmc);
Nach dem Aufruf enthält der Array @cusps die Häuserspitzen, @ascmc die besonderen trigonometrischen Punkte wie Vertex, Ko-Aszendent, äquatorialer Aszendent, wie in der Dokumentation
swephprog der Swiss Ephemeris beschrieben.
Ich will zunächst die Aufrufsyntax beschreiben. Man gibt das Startdatum als Julianisches Datum, dann die Anzahl der zu berechnenden Tage und schliesslich einen optionalen Array mit den Nummern der Planeten, die berechnet werden sollen. Der Vorschlagswert für letzteren ist der Array 0..9. Das Ergebnis ist die Liste der Positionen je Tag und Planet, gruppiert als Array von Arrays. Hier ein Beispielaufruf:
my $list; $list = SwissEph::get_long_list(2451544.50,5,[0,1,2]);Es sollen also die Positionen von Sonne, Mond und Merkur für fünf aufeinanderfolgende Tage ab dem Julianischen Datum 2451544.50 berechnet werden. Die Variable $list zeigt nach dem Aufruf auf einen Array der folgenden Form:
[
[
'279.859214548742',
'280.878647525955',
'281.898168142991',
'282.917754628557',
'283.93738357459'
],
[
'217.293284118158',
'229.31715475649',
'241.221851863712',
'253.060392323715',
'264.877744829482'
],
[
'271.111806759922',
'272.668068546155',
'274.229693578235',
'275.796854264239',
'277.369741944308'
]
]
Das Besondere an dieser Funktion ist, dass nun Referenzen auf neue Perl-Arrays im C-Code erzeugt werden müssen. Dies geschieht sehr einfach mit der Funktion newAV(). Alle anderen Konstrukte sind bereits in vorangehenden Beispielen vorgekommen.
AV *
get_long_list(jd_ut,n,...)
double jd_ut
int n
CODE:
SV* slon;
SV* srlon_av;
SV* sipl;
SV** sripl;
AV *ipl_av, *lon_av;
double xx[6],xjd;
int i=0,j,iflag = 0,ip=0,ipl_len,iret;
char serr[255];
RETVAL = newAV();
// Den neuen Array sterblich machen (GC erfasst keine Return Values)
sv_2mortal((SV*)RETVAL);
if( items > 2 ) ipl_av = (AV*)SvRV(ST(2));
else {
ipl_av = newAV();
for (i=0;i<10;i++) av_push(ipl_av,(newSViv(i)));
}
ipl_len = av_len( ipl_av );
for (i=0;i<=ipl_len;i++) {
sripl = av_fetch(ipl_av, i, 0);
if (sripl == NULL) break;
sipl = *sripl;
ip = (int)(SvIV(sipl));
lon_av = newAV();
sv_2mortal((SV*)lon_av);
for (j=0;j<n;j++) {
xjd = jd_ut + (double)j;
iret = swe_calc_ut( xjd, ip, iflag, xx, serr);
slon = newSVnv(xx[0]);
av_push(lon_av,slon);
}
srlon_av = newRV_inc((SV*) lon_av);
av_push((AV *)RETVAL, srlon_av);
}
OUTPUT:
RETVAL
#A93:Aadland,Beverley,f,16.9.1942g,11h45,Los Angeles -CA-,USA #B93:*,34n04,118w15,8hw,w #ZNAM:PST/W #ID:a1 #ATTRIB:DAV,m=FS,bem=704,src=RO,via=ROD&79 #LPOS:So23vi16,Mo11sg03,AC26sc36,MC6vi19Der mit dem Präfix #A93 eingeleitete Datenblock spielt eine Sonderrolle: Er steht am Beginn eines Datensatzes und darf innerhalb desselben nicht noch einmal vorkommen.
Wie erwähnt, ist Perl ideal geeignet, um Information aus Texten zu extrahieren, die nach bestimmten Regeln organisiert sind. Um dies zu verdeutlichen, folgt hier ein vollständiges Perl-Programm, das aus einer gegebenen Datei mit AAF-Daten je Datensatz das Kalenderdatum extrahiert und ausgibt. Beachten Sie, mit wie wenigen Zeilen Programmcode man eine Datei öffnen, auswerten und die gewünschten Daten ausgeben kann. Eine sehr schöne Sache an Perl ist, dass Sie nicht viel sagen müssen, um das zu sagen, was Sie sagen wollen, formuliert es Larry Wall.
my ($FH,@a); open(FH, "dav.aaf") or die "Kann Datei dav.aaf nicht öffnen: $!"; $/ = "#A93:"; # Satztrenner definieren <FH>; # "Ersten Satz" ignorieren, da er keiner ist while ( <FH> ) { @a = split /,\s*/, $_, 5; print "$a[3]\n"; }Auf die DAV-Datenbank angewandt, erhält man eine Programmausgabe, die wie folgt beginnt:
16.9.1942g 12.5.1914g 4.1.1933g 21.6.1921g 5.2.1934g ...
Hier ist der Code. Wir verwenden die Funktion get_planet_long des SwissEph-Moduls, weil sie ein bürgerliches Kalenderdatum als Input entgegennimmt.
use SwissEph qw(:Planets get_planet_long); # Beispielprogramm. Liest AAF-Horoskope ein und gibt die Verteilung # der Neptun-Stände in den Tierkreiszeichen aus. my @sign = qw(Ari Tau Gem Can Leo Vir Lib Sco Sag Cap Aqu Pis); my @neptun = (0) x 12; my ($FH,@a,$l,$total); open(FH, "dav.aaf") or die "Kann Datei dav.aaf nicht öffnen: $!"; $/ = "#A93:"; # Satztrenner definieren <FH>; # "Ersten Satz" ignorieren, da er keiner ist $total = 0; while ( <FH> ) { $total++; @a = split /,\s*/, $_, 5; $a[3] =~ /(\d+)\.(\d+)\.(-?\d+)([jg]?)/; $l = get_planet_long( Neptune, $3,$2,$1,12.,$4 eq 'j'? 0 : 1 ); $neptun[int($l/30)]++; } printf "%s : %5d\n", ($sign[$_], $neptun[$_]) for 0..11; print "\nSum : $total\n";Mit der Anweisung use SwissEph wird angekündigt, dass das Programm auf die Zusatzfunktionen der Swiss Ephemeris zugreift. Mit dem Zusatz qw(:Planets get_planet_long) werden die Planetensymbole und die Funktion get_planet_long() in den Namensraum importiert, so dass man beim Aufruf nicht das Präfix SwissEph:: dem Namen voranstellen muss. Das Einlesen der DAV-Datenbank geschieht wie oben. Mit einem regulären Ausdruck werden die Bestandteile des Kalenderdatums separiert, um sie in der folgenden Zeile der Funktion get_planet_long vorzulegen. Das Ergebnis, der ekliptikale Längenwert $l, wird durch den Ausdruck int($l/30) auf die Tierkreiszeichenposition reduziert, die als Index eines Arrays von Zählern verwendet wird. Das Programm erzeugt die folgende Ausgabe:
Ari : 2715 Tau : 3547 Gem : 3831 Can : 4014 Leo : 4210 Vir : 2773 Lib : 1652 Sco : 853 Sag : 919 Cap : 1182 Aqu : 1515 Pis : 1814 Sum : 29025
| Zum Anfang | Zurück zur Homepage |