User Tools

Site Tools


symbol_tables

The Nanoverse symbol tables

This page is accurate as of Nanoverse v1.0.0-a6. If you are using a later version, some aspects may have changed.

Are you sure you want to do this?

The Nanoverse dictionary contains all of the vocabulary needed to use Nanoverse to its fullest capabilities. It is highly recommended that you use the dictionary instead of crawling around in the symbol tables!

If, for some reason, you actually want to see how the Nanoverse compiler works, then this document will give you the information necessary to get your bearings.

Base package

In the document that follows, all packages are relative to nanoverse.compiler.pipeline.translate.symbol. So if I say control.run.ProjectSymbolTable, I actually mean:

nanoverse.compile.pipeline.translate.symbol.control.run.ProjectSymbolTable

Class vs instance symbol tables

The first thing to know is that there are two kinds of symbol tables. One is for a particular kind of thing, and describes the parameters of that thing. We call that an instance symbol table. The second kind of symbol table is called a class symbol table. A class symbol table contains a list of possible instances that you can use. We will give some concrete examples below.

Instance symbol tables

With one exception, all instance symbol tables have class names ending with InstSymbolTable. If you see that ending, you know with 100% certainty that this is an instance symbol table, and that it describes the parameters that a thing can take.

The one exception is ProjectSymbolTable, which is the top level symbol table. See "Project structure" for more on that particular table.

A more typical example is process.discrete.TriggerProcessInstSymbolTable, which represents the Trigger process. The Trigger process causes an agent or agents to perform an action.

Reading an instance symbol table

Look near the top of the file. There is a line that reads:

public class TriggerProcessInstSymbolTable extends DiscreteProcessInstSymbolTable<TriggerProcess> {

The part that follows extends is important. It tells you that this is no ordinary instance symbol table–it is a DiscreteProcess symbol table in particular. We will discuss the significance of that shortly.

Instance members (parameters)

The next method is called resolveMembers. This method is used to identify the parameters of this symbol. There is a line for each parameter–somewhere. Notice that the first line of this method is:

HashMap<String, MemberSymbol> ret = super.resolveMembers();

This says, in so many words, “take the list of members from this class' superclass and add the following to it.” The superclass is the word that follows extends in the line beginning public class. The superclass of TriggerProcessInstSymbolTable is DiscreteProcessInstSymbolTable. We'll be taking a look at that in a moment, but first let's finish with TriggerProcess.

We see that it lists four parameters:

behavior(ret);
skipVacantSites(ret);
requireNeighbors(ret);
filter(ret);

Let's look at filter in more detail.

Parameter definitions

The parameter filter is defined in the following method:

private void filter(HashMap<String, MemberSymbol> ret) {
  ResolvingSymbolTable rst = new FilterClassSymbolTable();
  MemberSymbol ms = new MemberSymbol(rst, "Filter for narrowing down " +
    "sites to be included in process.");
  ret.put("filter", ms);
}

There are three lines here. (One of them is broken into two halves, but it is still one “line” from Java's standpoint.) Pretty much every parameter definition looks just like this: three lines, each with a particular function.

Parameter class

ResolvingSymbolTable rst = new FilterClassSymbolTable();

This first line tells you exactly what kind of parameter is expected. In mathematics, we might define a real-valued function. If we supply an imaginary number, the behavior is undefined. Likewise, Nanoverse parameters expect exactly one kind of input, and they are undefined if you give anything else. This line tells us that we are expecting input of class Filter. The list of Filters is supplied in the FilterClassSymbolTable.

You can go directly to FilterClassSymbolTable by left-clicking on FilterClassSymbolTable and pressing Ctrl+B (or Cmd+B on a Mac). You can also right-click and choose “go to declaration.” We will discuss FilterClassSymbolTable below. First, though, let's look at the other two lines.

Parameter description

MemberSymbol ms = new MemberSymbol(rst, "Filter for narrowing down " +
  "sites to be included in process.");

The second line contains a plain-text description of the parameter. (Technically, it does more than that, but never mind.)

Identifier assignment

ret.put("filter", ms);

The third line says that you use the word filter is used to set this parameter. Generally, the name of the method (the word filter in private void filter…) is the same as this vocabulary item. If it's not, however, the word in quotes on the third line wins.

Instance table superclasses

Sometimes, when several symbol tables are very similar, their common properties are combined into a superclass, which they all extend. In that case, some of the parameters may be defined in the superclass. Go back to the declaration line of FilterProcessInstSymbolTable:

public class TriggerProcessInstSymbolTable extends DiscreteProcessInstSymbolTable<TriggerProcess> {

This class extends DiscreteProcessInstSymbolTable. If we click on DiscreteProcessInstSymbolTable and press Ctrl+B, we see that it also has a resolveMembers method. This method provides two more arguments: activeSites and maxTargets. We also see that DiscreteProcessInstSymbolTable extends ProcessInstSymbolTable, which provides two more arguments: start and period.

Putting it all together, we see that TriggerProcess actually has eight parameters:

behavior
skipVacantSites
requireNeighbors
filter
activeSites
maxTargets
start
period

Class symbol tables

The second type of symbol table we must consider is the class symbol table. As discussed earlier, each parameter has an expected data type; the class symbol table provides a list of classes that satisfy a particular type.

Encountering class symbol tables

Continuing with our example from the previous section, we wish to return to the filters argument of TriggerProcessInstSymbolTable.

private void filter(HashMap<String, MemberSymbol> ret) {
  ResolvingSymbolTable rst = new FilterClassSymbolTable();
  MemberSymbol ms = new MemberSymbol(rst, "Filter for narrowing down " +
    "sites to be included in process.");
  ret.put("filter", ms);
}

We recall that the first line specifies the type (interface) of the parameter. In referencing a class symbol table, we now understand that this first line provides a menu of options. Click on FilterClassSymbolTable and press Ctrl+B (Cmd+B). This is FilterClassSymbolTable, our first class symbol table.

Structure of a class symbol table

We see that FilterClassSymbolTable looks very similar to the smbol tables we've seen already, but with a few key differences. First, instead of resolveMembers, class symbol tables have resolveSubclasses. Like its instance counterpart, resolveSubclasses describes the set of values that can be provided. However, only one value will be chosen from the list–the instance value–and that value will have an instance symbol table.

Subclass definitions

Unlike parameter definitions, subclass definitions don't need to do much work. The plain-text description of each subclass is defined in its instance symbol table, so the subclass definition only needs to map a text identifier to an instantiable symbol table. In the example below, the first line specifies the type of instantiable symbol table to build (CellStateFilterInstSymbolTable), and the second line specifies the word that we use to build it:

private void cellState(HashMap<String, Supplier<InstantiableSymbolTable>> ret) {
    Supplier<InstantiableSymbolTable> st = CellStateFilterInstSymbolTable::new;
    ret.put("CellState", st);
}

The method above says that we are associating the word CellState with a CellStateFilter. In a Nanoverse program, this would look as follows:

Trigger {
  filters: CellState {
    // CellState parameters go here...
  };
};

Special symbol tables

Some arguments require multiple entries, either with or without names associated with each entry. These situations have their own handling in the symbol tables.

Dictionaries

Sometimes you want to associate a class with an arbitrary plain-text name. The most common situation where this occurs is in defining agent behaviors. To do this, we build a dictionary:

behaviors {
  behaviorName1 {
    // actions go here...
  };
  behaviorName2 {
    // actions go here....
  };
};

We can see a dictionary parameter in action if we look at the behaviors argument of agents.AgentDescriptorInstSymbolTable:

private void behaviors(HashMap<String, MemberSymbol> ret) {
    ClassSymbolTable cst = new ActionClassSymbolTable();
    ResolvingSymbolTable rst = new DictionarySymbolTable<>(cst, BehaviorMapLoader::new);
    MemberSymbol ms = new MemberSymbol(rst, "List of named behaviors and their corresponding action sequences.");
    ret.put("behaviors", ms);
}

We see that the dictionary parameter declaration involves two symbol tables. The first, a class symbol table, specifies what kind of value to associate with each plain text name:

ClassSymbolTable cst = new ActionClassSymbolTable();

The second says that we want a dictionary of such values.

ResolvingSymbolTable rst = new DictionarySymbolTable<>(cst, BehaviorMapLoader::new);

After that, we build the parameter as we do any ordinary parameter.

MemberSymbol ms = new MemberSymbol(rst, "List of named behaviors and their corresponding action sequences.");
ret.put("behaviors", ms);

Lists

Sometimes we want to allow more than one value for a parameter, but the values don't need to have names like in a dictionary. Instead, we just want a list. To do that, we create a list parameter, using a formula almost identical to that of a dictionary. Consider, for example, the reactions parameter of agents.AgentDescriptorInstSymbolTable:

private void reactions(HashMap<String, MemberSymbol> ret) {
  ClassSymbolTable cst = new ReactionClassSymbolTable();
  ResolvingSymbolTable rst = new ListSymbolTable<>(cst, ReactionStreamLoader::new);
  MemberSymbol ms = new MemberSymbol(rst, "List of interactions, if " +
    "any, between the agents being described and continuum " +
    "nanoverse.runtime.layers.");
  ret.put("reactions", ms);
}

Again, the first line specifies the kind of item that we want in each element of the list:

ClassSymbolTable cst = new ReactionClassSymbolTable();

The second line says we want a list of it:

ResolvingSymbolTable rst = new ListSymbolTable<>(cst, ReactionStreamLoader::new);

The remainder builds the parameter like all others:

MemberSymbol ms = new MemberSymbol(rst, "List of interactions, if " +
  "any, between the agents being described and continuum " +
  "nanoverse.runtime.layers.");
ret.put("reactions", ms);

Default values

One useful feature of Nanoverse is the ability to omit parameters and have a reasonable default supplied. Once we have our online dictionary, it will also be easy to see what that default value is. A the moment, though, it's a significant hassle. It is, however, quite formulaic, so once you get the hang of it, you shouldn't have much trouble.

  1. Identify the canonical name of your class of interest (i.e., the name of the class plus its full package name). For example, if you are interested in TriggerProcess, the canonical name is runtime.processes.discrete.TriggerProcess. You can find the canonical name of a class by pressing shift twice, typing its name, and pressing enter to open the file, then scrolling to the top line of the file.
  2. Drop the runtime prefix. So for TriggerProcess, you want process.discrete.TriggerProcess.
  3. Go to the compiler.pipeline.instantiate.loader package. The package hierarchy is identical to that of the runtime package. Follow this package hierarchy to the package of interest. So for TriggerProcess, go to compiler.pipeline.instantiate.loader.processes.discrete.
  4. In the appropriate package, there is a file called XyzInterpolator, where Xyz is the name of the class of interest. So for TriggerProcess, you're looking for TriggerProcessInterpolator.
  5. For each parameter associated with a particular instance symbol table, there is a method inside the interpolator. For example, the symbol table and the interpolator for TriggerProcess both have a method called filter.
  6. Look at the method for the parameter of interest. If it has a default for that parameter, there will be a line that says something like defaults.xyz() or defaults::xyz, where xyz is the parameter of interest. If there is no such line, the argument is required. For filters, we have the following: return defaults.filter(lm, p);
  7. If there is a default value, you can view it by left-clicking on the word defaults and pressing Ctrl+B (Cmd+B).
symbol_tables.txt · Last modified: 2015/11/17 11:34 by david