A toolkit for compilers and interpreters

Linked List of Symbol Tables

Consider this Scheme code:

(let ([x 5]
      [y 10])
  (+ x y (let ([y 8]) (+ x y)) y)
  )

In this code, x is always bound to 5 since it is declared just once. y is shadowed; two of its references are when it's bound to 10, once while bound to 8.

To handle these different scopes, CITkit provides a linked list of symbol tables which I call "environments". Start with a "null environment" which contains no bindings at all. This is to trigger "identifier not found errors". From this null environment, you can add on useful environments for real bindings.

In the example above, the Scheme interpreter would probably start with a null environment and an environment beyond that for the global scope so that + can be bound to the addition primitive:

( ([+ #<addition primitive>]) )

The first let extends the environment with another symbol table:

(
  ([x 5] [y 10])
  ([+ #<addition primitive>])
)

The second let extends it another time:

(
  ([y 8])
  ([x 5] [y 10])
  ([+ #<addition primitive>])
)

When y is requested, this first symbol table is triggered to get the value 8. When x is requested and not found in the first symbol table, the second symbol table is triggered, and 5 is returned.

When that inner let is done executing, the top environment is popped off, and the old value of y is restored!

Built in Factory for Testing

The IEnvironment<T> interface requires all environment implementers to know how to create a new symbol table for the top of the environment list with a create() method. This is primarily for easy testing since it's easier to mock a method call than a call to a constructor.