The LifeLines programming subsystem lets you produce reports in any style or layout. You may generate files in troff, Postscript, TeX, SGML or any other ASCII-based format, for further text processing and printing. You access the report generator by choosing the r command from the main menu. You may also use the programming subsystem to create query and other processing programs that write their results directly upon the screen. For example, there is a LifeLines program that computes the relationship between any two persons in a database.
Each LifeLines program is written in the LifeLines programming language, and the programs are stored in normal files. When you direct LifeLines to run a program, it asks you for the name of the program file, asks you where you want the program's output written, and then runs the program.
For example, say you want LifeLines to generate an ahnentafel. Such a report might look like:
Example 1-1. Example of ahnentafel report
1. Thomas Trask WETMORE IV
b. 18 December 1949, New London, Connecticut
2. Thomas Trask WETMORE III
b. 15 October 1925, New London, Connecticut
3. Joan Marie HANCOCK
b. 6 June 1928, New London, Connecticut
4. Thomas Trask WETMORE Jr
b. 5 May 1896, New London, Connecticut
d. 8 November 1970, New London, Connecticut
5. Vivian Genevieve BROWN
b. 5 April 1896, Mondovi, Wisconsin
6. Richard James HANCOCK
b. 18 August 1904, New London, Connecticut
d. 24 December 1976, Waterford, Connecticut
7. Muriel Armstrong SMITH
b. 28 October 1905, New Haven, Connecticut
8. Thomas Trask WETMORE Sr
b. 13 March 1866, St. Mary's Bay, Nova Scotia
d. 17 February 1947, New London, Connecticut
9. Margaret Ellen KANEEN
b. 27 October 1859, Liverpool, England
d. 10 May 1900, New London, Connecticut
... lots more
Here is a LifeLines program that generates this report:
Example 1-2. Example of ahnentafel report script
/* * @progname ahnentafel_tutorial.ll * @version 1.0 * @author Wetmore * @category sample * @output text * @description * * Generate an ahnentafel chart for the selected person (tutorial sample). */ proc main () { getindi(indi) list(ilist) list(alist) enqueue(ilist, indi) enqueue(alist, 1) while (indi, dequeue(ilist)) { set(ahnen, dequeue(alist)) d(ahnen) ". " name(indi) nl() if (e, birth(indi)) { " b. " long(e) nl() } if (e, death(indi)) { " d. " long(e) nl() } if (par, father(indi)) { enqueue(ilist, par) enqueue(alist, mul(2,ahnen)) } if (par,mother(indi)) { enqueue(ilist, par) enqueue(alist, add(1,mul(2,ahnen))) } } }
Say this program is in the file ahnentafel_tutorial. When you choose the r option from the main menu, LifeLines asks:
What is the name of the report program? enter string:
You enter ahnentafel_tutorial. Since the program generates a report, LifeLines asks where to write that report:
What is the name of the output file? enter file name:
You enter a file name, say my.ahnen. LifeLines reads the program ahnen, executes the program, and writes the report output to my.ahnen. LifeLines reports any syntax or run-time errors found while trying to run the program.
A LifeLines program is made up of procedures and functions; every program must contain at least one procedure named main. The main procedure runs first; it may call other procedures, functions and built-in functions. In the ahnentafel example there is only one procedure.
In the example program, there are some comments at the top, to tell the reader a bit about the program. The comments run from /* to */, and are not necessary (but are suggested).
A procedure body is a sequence of statements. In the example program, the first five statements are:
getindi(indi) list(ilist) list(alist) enqueue(ilist, indi) enqueue(alist, 1)
The first statement calls the
getindi
(get individual) built-in
function, which causes
LifeLines to ask you to identify a
person using the zip browse style of identification:
Identify person for interpreted report enter name:
After you identify a person, he or she is assigned to the
variable indi
. The next two
statements declare two list
variables, ilist
and
alist
. Lists hold sequences of
things; there are operations for placing things on lists,
taking things off, and iterating through the list elements. In
the example, ilist
holds a list of
ancestors, in ahnentafel order, who have not yet been reported
on, and alist
holds their respective
ahnentafel numbers.
The next two statements call the enqueue
function, adding the first members to both lists. The person
identified by the getindi
function is
made the first member of ilist
, and the
number one, this person's ahnentafel number, is made the first
member of alist
.
The rest of the program is:
while (indi, dequeue(ilist)) { set(ahnen, dequeue(alist)) d(ahnen) ". " name(indi) nl() if (e, birth(indi)) { " b. " long(e) nl() } if (e, death(indi)) { " d. " long(e) nl() } if (par, father(indi)) { enqueue(ilist, par) enqueue(alist, mul(2,ahnen)) } if (par, mother(indi)) { enqueue(ilist, par) enqueue(alist, add(1,mul(2,ahnen))) } }
This is a loop that iteratively removes persons and their ahnentafel numbers from the two lists, and then prints their names and birth and death information. If the persons have parents in the database, their parents and their parents' ahnentafel numbers are then put at the ends of the lists. The loop iterates until the list is empty.
The loop is a while loop statement. The line:
while (indi, dequeue(ilist)) {removes (via
dequeue
) a person from ilist
, and assigns the person to variable indi
. As long as there
are persons on ilist
, another iteration of the loop follows.
The statement:
set(ahnen, dequeue(alist))is an assignment statement. The second argument is evaluated; its value is assigned to the first argument, which must be a variable. Here the next number in
alist
is removed and assigned to variable
ahnen
. This is the ahnentafel number of the person just removed from ilist
.
The line:
d(ahnen) ". " name(indi) nl()contains four expression statements; when expressions are used as statements, their values, if any, are treated as strings and written directly to the report output file. The
d
function converts its integer
argument to a numeric string. The ". " is a literal (constant) string value. The name
function returns the
default form of a person's name. The nl
function returns a string containing the newline character.
The next two lines:
if (e, birth(indi)) { " b. " long(e) nl() } if (e, death(indi)) { " d. " long(e) nl() }write out basic birth and death information about a person. These lines are if statements. The second argument in the conditional is evaluated and assigned to the first argument, which must be a variable. The first if statement calls the birth function, returning the first birth event in a person's record. If the event exists it is assigned to variable
e
, and the body (the items between the
curly brackets) of the if statement is executed. The
body consists of three expression
statements: a literal, and calls to the long
and
nl
functions. Long
takes an
event
and returns the values of
the first DATE
and
PLAC
lines in the event.
Finally in the program is:
if (par, father(indi)) { enqueue(ilist,par) enqueue(alist,mul(2,ahnen)) } if (par,mother(indi)) { enqueue(ilist,par) enqueue(alist,add(1,mul(2,ahnen))) }
These lines add the father and mother of the current person, if either or both are in the database, to
ilist
. They also compute and add the parents' ahnentafel numbers to alist
. A father's ahnentafel
number is twice that of his child. A mother's ahnentafel number is twice that of her child plus one.
These values are computed with the mul
and add
functions.
The following is a good template to use when creating a new report from scratch.
/* * @progname reportname * @version Version Number. * @author report author and possible email address * @category ???? * @output Format of Report Output * @description The following paragraph is used to populate index.html. * * This report .... (Note, the text in the 1st paragraph following the @keyword * lines is used as a description in the automatically generated index.html * file. The text following the @description is not used for this purpose.) * The description lines can be written with or without the *'s on the left * they will be removed when generating index.html. * * Additional descriptive text */ proc main() { }
LifeLines programs are stored in files you edit with a screen editor. Programs are not edited from within
the LifeLines program; edit them as you would any text file. The programs may be stored in any
directories; they do not have to be kept in or associated with LifeLines databases. You may set the
LLPROGRAMS
shell variable to hold a list of directories that LifeLines will use to automatically
search for programs when you request program execution.
A LifeLines program is made up of one or more procedures and functions. A procedure has format:
proc name(params) { statements }
Name
is the name of the procedure, params
is an optional list of parameters separated by commas,
and statements
is a list of statements that make up the procedure body. Report generation begins with
the first statement in the procedure named main. Procedures may call other procedures and functions.
Procedures are called with the call statement described below.When a procedure is called, the
statements making up its body are executed.
A function has format:
func name(params) { statements }
Name
, params
and statements
are defined as in procedures. Functions may call other procedures and
functions. When a function is called the statements that make it up are executed. A function differs from
a procedure by returning a value to the procedure or function that calls it. Values are returned by the