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.
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:
table_count(tn)
Evaluates to the number of items within the table.
table_del(tn)
Delete the table named tn and all of its contents.
table_empty(tn)
Returns true if the table named tn is empty (has
no entries in it). It also returns true if the table does
not exist.
table_get(tn, en)
Get the value of the element named en in the
table named tn.
table_get(tn, en)
Get the value of the element named en in the
table named tn.
table_set(tn, en, value)
Set the value of the element named en in the
table named tn to value. If the table or the
element do not exist, they are created.
table_append(tn, en, value)
This is similar to table_set except value is
appened to any existing value in the entry. If the entry does not
exist, it is created.
table_unset(tn, en)
If the element en exists in the table tn
it is removed from the table. If the table does not exist, or
the element does not exist no operation is performed.
table_there(tn, en)
Returns true if the element en exists within
the table tn.
table_walk(tn, expr)
Iterate the table named tn and execute
expr for each element. When expr is executed,
the variable slot_name is bound to the name of the
element that is currently being iterated over and the variable
named slot_value is bound to the value of the
element that is currently being iterated over.
table_enum(expr)
Iterate all of the active tables executing expr for each
one. The variable table_name is bound to the name of table
during expr.
Lists are named entities containing zero or more elements (strings). Just as with tables, they are manipulated via built-in routines.
list(ln, [item1], [item2]>, ...)
Creates a list named ln. If provided, the list is initialized
with the remaining arguments.
list_len(ln)
Computes the number of items in the list.
list_nth(ln, pos)
Returns the selected element from the list. The elements are numbered beginning at zero.
list_del(ln)
Delete the list named ln and all of its contents.
list_exists(ln)
Returns true if the list named ln exists.
list_empty(ln)
Returns true if the list ln is empty.
list_concat(list_a, list_b)
Append copies of all the elements in list_b to list_a.
list_walk(ln, code)
Evaluate code for each entry in ln with the contents of
the list entry bound to list_item. If the list_item
variable is changed, the change is reflected in the list.
list_remove
This function can only be called from within list_walk. If called, the
current entry in the list (list_item) is deleted.
list_insert(value)
This function can only be called from within list_walk. It
inserts value as a list entry after the current node. The new
entry is not iterated over during the next cycle (it is silent).
list_push(ln, value)
An entry with a value of value is appended to the front of
ln.
list_pop(ln)
The front element of ln is removed and returned.
list_queue(ln, value)
An entry with a value of value is appended to the rear of
ln.
list_dequeue(ln)
The last element of ln is removed and returned.
list_enum(code)
Evaluate code for each list that is present binding the name of
the list to the variable list_name.
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))
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"
)
;
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)"
)
)
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.
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.