belatuk_symbol_table 5.2.0 copy "belatuk_symbol_table: ^5.2.0" to clipboard
belatuk_symbol_table: ^5.2.0 copied to clipboard

A generic symbol table implementation in Dart, with support for scopes and constants.

Belatuk Symbol Table #

Pub Version (including pre-releases) Null Safety License

Replacement of package:symbol_table with breaking changes to support NNBD.

A generic symbol table implementation in Dart, with support for scopes and constants. The symbol tables produced by this package are hierarchical (in this case, tree-shaped), and utilize basic memoization to speed up repeated lookups.

Variables #

To represent a symbol, use Variable. I opted for the name Variable to avoid conflict with the Dart primitive Symbol.

var foo =  Variable<String>('foo');
var bar =  Variable<String>('bar', value: 'baz');

// Call `lock` to mark a symbol as immutable.
var shelley =  Variable<String>('foo', value: 'bar')..lock();

foo.value = 'bar';
shelley.value = 'Mary'; // Throws a StateError - constants cannot be overwritten.

foo.lock();
foo.value = 'baz'; // Also throws a StateError - Once a variable is locked, it cannot be overwritten.

Visibility #

Variables are public by default, but can also be marked as private or protected. This can be helpful if you are trying to determine which symbols should be exported from a library or class.

myVariable.visibility = Visibility.protected;
myVariable.visibility = Visibility.private;

Symbol Tables #

It's easy to create a basic symbol table:

var mySymbolTable =  SymbolTable<int>();
var doubles =  SymbolTable<double>(values: {
  'hydrogen': 1.0,
  'avogadro': 6.022e23
});

// Create a new variable within the scope.
doubles.create('one');
doubles.create('one', value: 1.0);
doubles.create('one', value: 1.0, constant: true);

// Set a variable within an ancestor, OR create a new variable if none exists.
doubles.assign('two', 2.0);

// Completely remove a variable.
doubles.remove('two');

// Find a symbol, either in this symbol table or an ancestor.
var symbol = doubles.resolve('one');

// Find OR create a symbol.
var symbol = doubles.resolveOrCreate('one');
var symbol = doubles.resolveOrCreate('one', value: 1.0);
var symbol = doubles.resolveOrCreate('one', value: 1.0, constant: true);

Exporting Symbols #

Due to the tree structure of symbol tables, it is extremely easy to extract a linear list of distinct variables, with variables lower in the hierarchy superseding their parents (effectively accomplishing variable shadowing).

var allSymbols = mySymbolTable.allVariables;

We can also extract symbols which are not private. This helps us export symbols from libraries or classes.

var exportedSymbols = mySymbolTable.allPublicVariables;

It's easy to extract symbols of a given visibility:

var exportedSymbols = mySymbolTable.allVariablesWithVisibility(Visibility.protected);

Child Scopes #

There are three ways to create a new symbol table:

Regular Children #

This is what most interpreters need; it simply creates a symbol table with the current symbol table as its parent. The new scope can define its own symbols, which will only shadow the ancestors within the correct scope.

var child = mySymbolTable.createChild();
var child = mySymbolTable.createChild(values: {...});

Depth

Every symbol table has an associated depth attached to it, with the depth at the root being 0. When createChild is called, the resulting child has an incremented depth.

Clones #

This creates a scope at the same level as the current one, with all the same variables.

var clone = mySymbolTable.clone();

Forked Scopes #

If you are implementing a language with closure functions, you might consider looking into this. A forked scope is a scope identical to the current one, but instead of merely copying references to variables, the values of variables are copied into new ones.

The new scope is essentially a "frozen" version of the current one.

It is also effectively orphaned - though it is aware of its parent, the parent scope is unaware that the forked scope is a child. Thus, calls to resolve may return old variables, if a parent has called remove on a symbol.

var forked = mySymbolTable.fork();
var forked = mySymbolTable.fork(values: {...});

Creating Names #

In languages with block scope, oftentimes, identifiers will collide within a global scope. To avoid this, symbol tables expose a uniqueName() method that simply attaches a numerical suffix to an input name. The name is guaranteed to never be repeated within a specific scope.

var name0 = mySymbolTable.uniqueName('foo'); // foo0
var name1 = mySymbolTable.uniqueName('foo'); // foo1
var name2 = mySymbolTable.uniqueName('foo'); // foo2

this Context #

Many languages handle a sort of this context that values within a scope may optionally be resolved against. Symbol tables can easily set their context as follows:

void foo() {
  mySymbolTable.context = thisContext;
}

Resolution of the context getter functions just like a symbol; if none is set locally, then it will refer to the parent.

void bar() {
  mySymbolTable.context = thisContext;
  expect(mySymbolTable.createChild().createChild().context, thisContext);
}
0
likes
160
points
473
downloads

Publisher

verified publisherdukefirehawk.com

Weekly Downloads

A generic symbol table implementation in Dart, with support for scopes and constants.

Repository (GitHub)
View/report issues

Documentation

API reference

License

BSD-3-Clause (license)

Dependencies

collection

More

Packages that depend on belatuk_symbol_table