THE LIFELINES PROGRAMMING SUBSYSTEM AND REPORT GENERATOR

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-16. 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-17. Example of ahnentafel report script


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 ahnen. When you choose the r option from the main menu, LifeLines asks:

What is the name of the report program?
enter string:

You enter ahnen. 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.

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.