Next Previous Contents

5. CGL Data structures

Rather than building in support for typing in CGL, data structures are managed through the use of run-time functions. Most of these functions maintain the data structures in their own name space.

5.1 Tables

One data structure that CGL supports is a table (a collection of named strings). If the keys are numeric, then tables can simulate arrays. Each table has its own identifier that lives in its own name space. The following routines manipulate tables:

5.2 Lists

Lists are named entities containing zero or more elements (strings). Just as with tables, they are manipulated via built-in routines.

5.3 Structures

CGL uses two primitives to support structures (sometimes referred to as ``records''). Records are encoded as strings so they can fit within CGL variables and return values.

To construct a record, the record built-in is used. This function expects an even number of arguments and at least two of them. As an example, we will create a few records of people and add them to a table.


  table_set(~people, "Joe", 
           record(~age, 10, ~height, 6, ~weight, 180))
  table_set(~people, "Monica",
           record(~age, 24, ~height, 5, ~weight, 170))
  table_set(~people, "Jeff",
           record(~age, 27, ~height, 6, ~weight, 215))

The record built-in converts the name-value pairs into a string that can be stored in the table people.

In order to use a record we use the with procedure which temporarily binds the name-values pairs as CGL variables. So we can make a print person procedure to format the information on a person:


  proc PrintPerson(1) = 
    name = (arg(1))

    with
    (
     table_get(~people, name),

     "Name:   " name   "\n"
     "Age:    " age    "\n"
     "Height: " height "\n"
     "Weight: " weight "\n"
    )
    ;

5.4 Sorting

Sorting is done by compilers all the time. CGL has a very efficient mechanism for sorting lists of objects. This is done with a specific form of CGL procedure calls. As a simple example, let us sort a list of movie titles, some of which have associated ratings.


  counter = (0)
  sort
  (
    /* First parameter: Code to build the list. */
    map (counter)
    [
      0: { sort_add("Office Space", 10)        };
      1: { sort_add("The Wall", 10)            };
      2: { sort_add("Plan 9 from Outer Space") }; /* No Rating */
      3: { sort_add("Into the Night", 9)       };
      4: { sort_add("WarGames", 9)             };
    ]
    counter = (add(counter, 1)),

    /* Second parameter: Comparison function. */
    strcmp(key_a, key_b),

    /* Third parameter: Output processing. */
    "Movie: " cur_key " "
    if
    (
      is_defined(~cur_value),
      "Rating: " cur_value,
      "(Not Rated)"
    )
  )

All of the sorting takes place within the context of the sort routine, which takes three arguments. The first argument is a body of code that is executed repeatedly to build the list of items to sort. The second is the actual comparison function that is used to order the elements. The third is the code that is executed over the output list.

In the above example, the list is populated by keeping a counter to count the number of times the code is executed. Of course, in a real world example, this list would presumably come from some other iteration. The code is executed until the sort_add procedure is not called during the execution or until the sort_stop procedure is called. During each execution, sort_add may be called as many times as required. The second parameter to sort_add is optional and is a value associated with the key that is being sorted.

The example does a simple string sort by using the strcmp routine as a comparison function. The comparison code is executed as many times as necessary with the variables key_a and key_b being bound to the two keys to compare. The code must evaluate to a value lower than 0 if key_a is less than key_b or greater than 0 if key_a is greater than key_b or 0 if the two keys are equal.

Once the list is sorted, the list that was built during the first phase is iterated over and for each entry in the list the output code is executed. During the execution the variable cur_key is the current value of the key. If a value was provided then the variable cur_value is bound to the current value. If no value is provided, then cur_value is unbound before execution of the output code.

5.5 Complex data structures

Sometimes it is necessary to manage much more complex data structures than simple lists and associative arrays. CGL does this by combining the properties of its basic data structures to form more complex structures.

For example, a tree or directed graph can be constructed by having a list or table where each entry contains the name of another list or table. For dynamic structures, the built-in routine uniq can generate a unique identifier for naming the components of data structures.

This unique identifier ends up functioning like a pointer in other languages. Garbage collection can be easily done by appending prefixes to the unique identifiers so that they can be identified by walking the available lists of identifiers (table_enum, list_enum, var_enum). The match_pat function can be used to quickly discard items that are no longer used.


Next Previous Contents