Code overview of the Chu space calculator, java version.

The Java/GUI Chu Space calculator was developed from Feb. '97 to June '98 by the author of this document (Larry Yogman). This document is intended for anyone who wishes to extend or modify the calculator, or reuse some of its parts. The coding was purely a one-man show, so this is the first time that any extensive documentation has been written. I hope that this overview helps you to find your way around, and to comprehend why things are the way they are.

I'm moving, and I don't yet have a new email address or phone number, although I'll try to remember to send Vaughan these things when I get them. If you have a question that this document and the source code don't answer, and you don't have any direct way to contact me, phone my parents at (412)-833-1401 and ask them how to contact me.

View from 10,000 feet

Functionality

The calculator allows a user to view Chu spaces as grids of numbers, to create and edit them by typing these grids, to manipulate them with a variety of unary and binary operations, to store them in named variables, to write and execute scripts of sequential operations, and to cancel a calculation that is taking too long. Chu spaces can have any K from 0 to 10, and any number of rows and columns, including zero. Operations can be performed with or without eliminating repeated rows and columns ("standardization").

I don't attempt to explain the mathematics of Chu spaces in my code or this document. For interactive lessons on Chu spaces, try Living Chu spaces.

Language

The calculator is written in Java 1.1.1, and uses nothing but the standard libraries. The GUI stuff is now pure Java 1.1 AWT: no "deprecated" 1.0 method calls remain. Anonymous inner classes are used heavily, and not just for event handling, so I hope you're comfortable with them. If you're a LISP hacker, think of them as verbose lambda expressions.

Programming Philosophy

The code is written in a way that tries to minimize side effects and allow optimizations. For example, Chu spaces are constructed, stored, and eventually forgotten, but they are never modified during their lifetime. This allows optimizations, like keeping a pointer to the standardized version of a space, and comparing object pointers to see if a value has changed.

Another example: the bindings of names to values does not change when the Context (K or standardization) changes. Instead, names are bound to things that know how to conform to whatever Context is currently in use. Spaces conformed to the current Context are built only as needed.

I have also done my best to provide lots of flexability by making code more general than it needs to be. I'll point out opportunities for extension or optimization as I come to them.

Major modules

Grand Central Station is Calc.java . It is here that all the bindings of names to spaces or operations or whatever are stored. It is here that calculations are launched. It is here that components must register to hear broadcasts about the progress of a calculation. The other .java files implement the operations, interpret the scripting language, and provide the user interface. Calc.java holds everything together. Here's an outline of all the .java files, and of the rest of this document:

Miniature classes

Below are some very small classes which are used routinely by the rest of the code. Most of them are used in many places, and thus need to be introduced up front.

ExecutionException.java

I throw an ExecutionException to report any sort of soft failure encountered while performing a script or otherwise manipulating the state of a Calc object. Typical ExecutionExceptions are: unbound variables, operations not defined for the current Context, illegal values for a variable, etc.

I handle most Exceptions, including ExecutionExceptions, in the same simple way: I pass them up to the user by printing their message in the most convenient TextComponent.

SyntaxException.java

I throw a SyntaxException whenever an attempt to parse a script fails. For the details, see Scripting language.

Context.java

The current Context consists of an integer for the current value of K and a boolean which is true when standardization is on. A Context object is used to store this state, and many Operators take the current Context as an argument.

Conformable.java

A Conformable object is just a context-sensitive Chu space. Ordinary Chu spaces conform by standardizing when standarization is on, but special constants like one or bottom are sensitive to K. These special conformable objects are defined, constructed and registered with the other constants in Calc.java. Values get conformed to the current Context whenever they are read out of a Calc object.

UnaryOperator.java

A UnaryOperator is a context-sensitive function Chu-->Chu. By default, the context sensitivity is implemented by performing a context-insensitive operation and then conforming the output. However, this behavior can be overridden to provide other context-sensitivity. For example, if you wanted to make the bang operation depend on the Context's value of K, you would start by overriding the method Chu apply(Chu arg, Context context) where bang is defined, constructed and registered, with the other UnaryOperators in Calc.java.

BinaryOperator.java

A BinaryOperator is a context-sensitive function Chu*Chu-->Chu. As with UnaryOperators, the default "sensitivity" just consists of conforming the output. As with UnaryOperators, this behavior can be overridden by providing a different implementation of Chu apply(Chu leftArg, Chu rightArg, Context context).

Executable.java

An executable is any sequence of operations that can be performed on a Calc object. Statements and Programs from the scripting language are executables. In addition, object for changing standardization are defined, constructed, and registered as executables in Calc.java

Calc.java

A Calc object has two major functions. First, a Calc object holds the whole state of a calculator, both the current Context and all bindings of names to variables, constants, operators, and executables. Second, a Calc object keeps track of calculations (running Programs), and broadcasts the news when calculations start, finish, or fail. (There will generally be only one Calc object in an application, but I saw no reason to insist on this and make everything in Calc.java static)

I put these two functions together, and separated them from the user interface, because I wanted to break the UI down into small independent components. These components can learn all they need to know by consulting the Calc object. They do not have to store state themselves or coordinate with each other.

Bindings and state

A Calc object remembers the state of a calculator, and protects that state from illegal modification. For example, it stores the current Context in a field called "context", allows this context to be read and altered, but throws an ExecutionException if asked to set K to a negative value (method setK).

Most of the state of a calculator is stored in Hashtables which bind names to objects. The Calc constructor builds these tables and fills them with the standard initial contents. Many important objects are defined here in the Calc constructor, using anonymous inner classes.

Variables and Constants

The Hashtables named "constants" and "variables" are used to store bindings of names to Chu spaces. As mentioned above, what is actually stored in these tables are Conformable objects, which know how to provide a Chu space, given a context. Most Conformable objects are really just Chu spaces (which conform by standardizing if needed). In addition, there are special Conformable objects which are defined in the Calc constructor using anonymous inner classes.

The Conformable object "undef" (used to represent the lack of a binding) conforms by returning null. The table "variables" contains only Chu spaces or "undef". The table "constants" also contains mostly Chu spaces, but the constants bound to "1" and "_|_" conform by returning a Chu space which depends on K. Users of Calc.java do not have to worry about Conformable objects, because Conformable objects are never passed to or returned from Calc.

There are many methods for manipulating constants and variables. To check that a proposed variable name does not conflict with an existing binding, use "newVariableNameOK" (see ChuTarget.java). To create or change bindings of a variable, use "bindVariable" (see, for example, Statement.java). To find all variable names (possible targets for a result space), use "lvalueIdentifiers" (see, for example ChuTarget.java). To find all bound names (possible sources for an input space), use "rvalueIdentifiers" (see ChuSource.java). To get the (conformed) value of any binding, use "lookupChu" (see, for example, ChuSource.java). To get the underlying (unconformed) value of a variable, use "lookupVariable" (see ChuEdit.java). Since constants may have no unconformed value, there is no "lookupConstant".

Unary and Binary Operators

Chu.java provides only a minimal, orthogonal set of operators on Chu spaces. These basic operators, plus others built up from them, are all entered into Hashtables named "unops" and "binops" in the Calc constructor.

For example, Chu.java defines product but not sum. The Calc constructor defines a BinaryOperator which dualizes the inputs and outputs of product, then stores this Operator in binops, under the name "+". The Calc constructor also defines a BinaryOperator which just calls product, and stores this in binops under the name "*".

Executables

The Hashtable "executables" holds Executable objects. General machinery for looking up an Executable by name and executing it is already built (see Statement.java, particularly class InvokeStatement). At the moment, the only entries in this table are operations which change the current standardization. However, if you ever want to support scripts which can be invoked by name, just provide a way for the user to enter Programs into this table.

Calculation management

A Calc object manages calculations being performed on itself. Given a piece of text, it forks a thread to run that text (see method "calculate"). That thread parses the text into a runable Program, runs the program, and keeps all components of the UI informed about the progress of the Program (see method "run" of inner class "Calculation").

Observer/Observable

Communication between Calc objects and the UI use java.util's Observer/Observable mechanism. Calc extends Observable, and all UI components implement an Observer. The method "broadcast" is used to notify all Observers of the beginning, ending, or failure of a calculation.

The type of event which has occurred is given by the field "eventCode". Observers typically use a switch statement on this field to decide how to handle an event. Look at the "update" method of the Observer for GUI.java for an example. Notice that the argument passed to update is either a String (the text of the Program) or null (If the Program is long).

MultiThreading

Some Chu space calculations take a long time. This is partly the fault of Java, partly due to the intrinsic complexity of these operations, and partly due to a lack of cleverness on the part of the programmer (yours truly). In any case, the user needs a way to abort a calculation in progress. If the calculation ran in the same Thread as the rest of the calculator, then the whole interface would lock up until the calculation finished.

Therefore, I fork a separate Thread, called "calcThread", to handle the heavy computational work. The main Thread continues running, and can respond to a user's click on the cancel button by calling method "cancel" in Calc.java. This method halts calcThread, throws it away, and broadcasts the news that a calculation failed. Notice that nothing in the scripting language or the back end Chu space operations needs to know anything about multithreading or the possibility that a calculation might "fail" due to user impatience.

Multithreading is done in the usual Java way, by passing a Thread constructor something that implements a "run" method. In this case, class Calculation implements "run", and the constructor for both a Calculation and its associated Thread are called in method "calculate". I made Calculation an inner class, because it is used inside class Calc only, and because it calls "broadcast" often.

Chu.java

Chu objects represent Chu spaces. Most of the data about a Chu space is stored as a two dimensional array of numbers called "matrix". The rows of this matrix are events (or "points") , and the columns are states.

The fields "nrows" and "ncols" count the rows and columns. Notice that while nrows=matrix.length is redundant, ncols=matrix[0].length is not redundant, because we might have nrows=0, in which case there is no matrix[0]. For simplicity and symmetry, I've included both fields.

The field "K" gives the number of distinct degrees of completion. The entries in the matrix are in the range 0..K-1 and give the degree of completion of that event in that state. Notice that "K" is only partially redundant: The contents of the matrix provide a lower bound on K, but not an upper bound.

The last field is "standard", a pointer to the standardized version of this Chu space. For more information on this field, see Conform and standardize

Chu.java provides only basic Chu space functionality: In Calc.java, more complex operations are built up from these basic operations (see Unary and Binary Operators). These basic operations include constructing Chu spaces, converting spaces to and from Strings ("parsing"), and applying unary and binary operators which return new spaces.

The basic operations "implication" and "query" are much more complicated than the others, because they involve generating matrixes of overlapping rows and columns. Below, I deal with construction and parsing first, all the simple operations second, and implication and query last.

As in Calc.java and Matrix.java, all data is private: inspectors are provided as needed. Since Chu spaces are never modified, I provide no operations for changing their data.

Construction and Parsing

In order to work with Chu spaces, they must first be built. There are two small constructors for this purpose. First, there is a private constructor, available only inside Chu.java, which just builds a Chu object from given data, no questions asked. The Chu space operations generally call this private constructor to build the result of the operation. Second, there is a constructor for the tensor unit "1", which is just a 1 by K Chu space with one instance of each possible entry, 0..K-1.

In addition to these small constructors, there is a parse constructor which builds Chu spaces from String input, and an "unparse" method which turns the matrix of a space back into text. I throw a Chu.ParseException whenever there is a problem with parsing or unparsing a Chu space.

The parse constructor takes four Strings as input. The first three strings provide values for K, nrows, and ncols, and the last String provides the matrix itself.

The lines of the matrix text are newline-delimited, and the entries are the digit characters '0'..'9', possibly separated by spaces or tabs. Currently, there is no way to parse for K>10. A reasonable extension might be to allow K>10 but require whitespace between entries, which could then have more than one digit. The unparse method also currently assumes that the matrix entries correspond to a digits '0'..'9', and prints them without intermediate whitespace. (The rest of the code assumes that K may be any positive integer: only parsing and unparsing are restricted to digits.)

If the value of K specified by the input String conflicts with a matrix entry, I throw a Chu.ParseException. If the value of nrows or ncols specified by the input Strings conflict with the shape of the matrix, then I crop the matrix to size or pad with zeroes as needed.

If K, nrows or ncols is left unspecified (by passing null), then I set a boolean to indicate that the corresponding value is not initialized, and fill in a default value later. The default value of K is the largest entry in the matrix, plus one. The default value of nrows is the number of newline-delimited lines in the matrix text. The default value of ncols is the number of entries in the first row of the matrix, or zero if there are no rows in the matrix.

Simple Operations

In this section I describe the implementation of all the simple operations, first dual, choice, product, and sequence, then the conform and standardize operations. In the next section, I describe the implementation of implication and query.

Dual, choice, product, sequence

The unary operation "dual" just turns rows into columns and columns into rows. The implementation is very simple. First build a new matrix, then fill it, then call the private constructor to build the result. All the other operations perform these same steps, although some operations may have to do most of their work before they build the matrix, in order to know how large a matrix to build.

The binary operation "choice" sticks together two spaces by their corners, and pads out the matrix with zeros. That is, the rows of the new matrix are rows from the first space followed by zeros, or rows from the second space preceded by zeros.

The binary operation "product" combines the rows of two spaces in all possible ways. That is, the rows of the new matrix are rows from the first space followed by rows from the second space. All possible pairs of rows are formed.

The binary operation "sequence" combines the columns of two spaces, but not in all possible ways. Columns are partially ordered by component-wise comparison. A column is "initial" if it is "larger" than no other column. A column is "final if it is "smaller" than no other column. Columns of the new matrix are any column of the first space followed by an initial column of the second space, or a final column of the first space followed by any column of the second space.

The implementation of sequence first calls the helper function "classifyCols" to determine which columns are initial and which are final (classifyCols in turn calls its helper function, "compareCols"). Columns classified as "unknown" are comparable with nothing, and are therefore both initial and final. Once the columns are classified, the implementation then considers all possible pairs of columns twice. In the first pass, it counts all combinations that will go into the result. Now the matrix can be built. In the second pass, all the good combinations are written into the result.

Conform and standardize

Chu spaces are Conformable objects, so class Chu need a "conform" method that returns a version of this space conformed to the current Context. Chu spaces conform very simply, by returning their standardization if standardization is on, and returning themselves otherwise.

Because conform is a common operation, so is "standardize". Unfortunately, standardizing a large space can be very expensive. This pointer to the standardized version is manipulated only in the private constructor, in dual, and in standardize. Therefore, every space keeps a pointer to its standardized version (in the field "standard"), so the standardization needs to be computed at most once. Note that this optimization depends on the fact that Chu spaces are never modified. Spaces known to be standardized point to themselves, and spaces whose standardization is not yet known point to null.

The operation "standardize" eliminates all duplicate rows and columns. The helper functions row_sort and col_sort sort the indexes of the rows/columns. Sorting puts duplicates right next to each other, where they can be eliminated. The sorting functions return the number of unique rows/columns, and fill an array with their indexes. Once the unique row and column indexes are available, a new matrix can be built (we know how big it is). To fill in the matrix, just try all possible combinations of unique rows and columns, and copy the corresponding entry.

Implication and query

In this section I describe the implementations of implication and query. Both of these operations involve forming grids of overlapping rows and columns. To take the implication of two spaces A and B, form a matrix whose columns are columns of A and whose rows are rows of B. Each such matrix becomes a row of (A implies B). To take the query of a space A, form all possible square matrixes whose rows and columns are all rows of A, add the diagonals of these matrixes to the rows of A, and repeat until no new rows are generated. My implementations of implication and query use the same general way of generating matrixes of overlapping rows and columns.

Node.java, Link.java Tree.java

In order to generate matrixes quickly, I first put the sets of lines (rows or columns) in a form optimized for searching. The most common operation needed during searching is the extending of a valid prefix. A valid prefix is a sequence of entries which is an initial segment of some line in the set of lines. The commonly asked question is: given a valid prefix, what entries can I append to it to get a new valid prefix, one element longer? Either there is at least one such extension, or the "prefix" is already a full-length line. The Tree, Node, and Link classes implement a "prefix tree" data structure which is designed to implement prefix extension quickly.

Every Node represents some valid prefix. The "children" of a Node are Nodes representing extensions of this prefix (if a given index is not a valid extension, then the child in that position is null). The "parent" of a Node is a Node representing the prefix which this Node extends. The "branch" of a Node is the last element of this prefix, and also the position of this Node in its parent's array of children.

There are two important operations in class Node. First, the operation "child" implements prefix extension. It returns a Node representing a given extension of this prefix, or null if the extension is not valid. Second, the operation "grow" is used to build up the prefix tree. It extends the current prefix by adding a child Node for the given extension, unless that child already exists.

Leaf Nodes represent complete lines, and thus have no children. However, they do have "data", a pointer to a Link. A Link is a linked list of integers. The "data" for a leaf Node is the indexes of all matching lines. If the space is standardized, then there are no duplicate lines, so this linked list has length one. In general, there may be repeated rows or columns, so there may be more than one entry in "data".

A Tree is basically a convenient wrapper for the root Node of a prefix tree ("top"). A Tree remembers how long lines are supposed to be ("length"), and remembers the K value of the set of lines, which is the number of possible children of a Node ("arity"). Class Tree provides two operations, "findLine" and "addLine", which search and modify the set of lines. These operations return a Link, a linked list of the indexes of matching lines.

MatrixGenerator.java

Class MatrixGenerator uses the prefix trees described above to generate all possible matrixes of overlapping lines. To use this class, first pass the constructor two Trees, one for the rows and one for the columns of the matrix. Then call "next" repeatedly to get all the matrixes. As soon as "next" returns false, there are no more matrixes. Currently, MatrixGenerator is used only inside the implementations of implication and query. However, it would be very easy to use MatrixGenerator to find all morphisms between two spaces.

After a call to "next" that returns true, look in "rowLinks" or "colLinks" to get the new matrix. The array "rowLinks" has one linked list for each row. The numbers in this list are the indexes of all the lines that match that row. (Naturally, "colLinks" does the same for columns). To get an entry (r,c) of the matrix, just look up any line in the list for row r, and find the element in position c.

Why not just provide the matrix itself, instead of these linked lists of indexes? First, the whole matrix is sometimes more information than is needed: query needs only the diagonal of each matrix, so there is no point in building more. Second, the matrix is sometimes less information than is needed: implication needs to know how many instances of each matrix to put in the answer, and thus needs to know the length of the linked lists. Also, if you want to use MatrixGenerator to find morphisms between two spaces, the rowLinks, colLinks form is more explicit: row r goes to row rowLinks[r].datum(), and similarly for columns.

MatrixGenerator does a depth-first search of all possible matrixes. Since the number of possible matrixes grows exponentially, it is important to prune the search tree as early as possible. MatrixGenerator works by trial extension of an region of overlapping partial rows and columns. Every new cell added to the region extends one row prefix and one column prefix. The arrays rowNodes and colNodes keep track of the prefixes for all rows and columns, and allow non-valid prefix extensions to be detected quickly. The order in which cells are added to the region ("herringbone" order) is also chosen to allow impossible combinations to be detected quickly (see the code for details of "herringbone" order).

Whenever a forward step in herringbone order takes the search out of bounds, this means that all the cells in the matrix have been filled, and a new matrix (or morphism) has been found. If a backwards step in herringbone order is attempted from cell (0,0), then the depth-first search has backed up to the beginning, and there are no more matrixes.

Before tinkering with MatrixGenerator, be very careful to think about boundry cases like rows or columns of length zero, or sets of lines for which there are no matrixes. Experiments to date indicate that the current implementation handles these cases correctly, but earlier implementations were buggy.

Implication and Query

The implementations of implication and query use the MatrixGenerator described above.

The binary operation "implication" has as its rows the morphisms from A to B. To generate these morphisms, a MatrixGenerator is constructed using the rows of B and the columns of A. Each new matrix found corresponds to several morphisms, one for each possible way to pick rows and columns to form that particular matrix. The linked lists in rowLinks and colLinks give all the choices for each row and column, so the product of their lengths is the number of morphisms corresponding to this matrix. For each matrix, the implementation counts instances, constructs a row, and enters the right number of copies of that row into the result.

The unary operation "query" has rows which are closed under two operations. First, all rows of constants are in query, and second, all square matrixes which can be formed from the rows has its diagonal among the rows. The implementation adds all constant rows first, then repeatedly uses MatrixGenerator to find all matrixes and add their diagonals to the set of rows. There are two representations for the set of rows: a Tree for use with the MatrixGenerator, and a Vector for quickly finding entry values and for building the final result. When all diagonals are found to already be in the set of rows, then all rows have been found.

Query is a very expensive operation, because it uses MatrixGenerator repeatedly. Fortunately, there is an optimization if K=2. In this case, the diagonals of matrixes are all just unions and intersections of rows, so query can be computed without using MatrixGenerator at all. The function "query2" handles this special case.

Scripting language

The scripting language provides a uniform way to handle most communication from the UI to the rest of the application (pass a String to method "calculate" of a Calc object). The scripting language also allows the user to do all of the following: The user interface for the scripting language is in ScriptPanel.java. In this section, I describe the language itself.

In general, the language is processed in two stages. In the first stage, a String is parsed down to a Program containing the names of spaces and operators. This stage may throw a SyntaxException if the String is unparsable. In the second stage, the Program gets executed against a Calc object. This stage may throw an ExecutionException if some name is not bound to a value or something else goes wrong at runtime. I chose this division of labor so that future versions of the calculator might store unexecuted Programs while still doing as much error checking as possible before the Program is stored.

Expression.java

Expressions are things that get evaluated against a given Calc object and return a Chu space (method "eval"). There are three kinds of Expression: Identifiers, UnaryExpressions, and BinaryExpressions. An Identifier is just the name of some Chu variable or constant. It gets evaluated by looking up the corresponding space in the Calc object's tables. A UnaryExpression is a unary operator name plus an Expression, and gets evaluated by looking up the operator, evaluating the Expression, and applying the operator to the Expression. Naturally, BinaryExpressions are just like UnaryExpressions, only with two arguments. Note that Expressions are recursive: If compound Expressions could be built, they would be evaluated correctly.

The textual representation of Expressions does not exploit the full recursive generality described above. The simple "parse" function in class Expression currently assumes that the arguments of operators are all Identifiers. A more complex parser might handle nested, parenthesized expressions. Also, the current parser needs whitespace to find the separations between identifiers and operator names. A more clever parser might be able to do without this help. However, the problem is not simple, because (for example) the unary operator dual and the constant space bottom both have the same textual representation: "_|_".

Statement.java

Statements are the building blocks of the scripting language. Naturally, Statements are Executable. There are three kinds of statements: AssignStatements, InvokeStatements, and SetKStatements.

AssignStatements are the heart of the scripting language. Every AssignStatement has a "lhs" (left hand side) String which names a variable, and a "rhs" (right hand side) Expression. To execute an AssignStatement, just evaluate the Expression and call "bindVariable" to store the result in the variable. The textual representation of AssignStatements is just var=expr . The parse function knows the difference between AssignStatements and other Statements by the presence of the equals sign.

InvokeStatements consist of just the name of an Executable. They are evaluated by looking up that Executable in the tables of a Calc object, then executing that Executable against that Calc object. Currently, the only InvokeStatements are "Big", "little", and other names bound to the Executables that turn standardization on and off.

SetKStatements fall between the cracks left by the other two kinds of Statements. They cannot be AssignStatements, because what is being altered is not a Chu space variable. They cannot be InvokeStatements, because there are as many ways to set K as there are new values for K. Therefore, SetKStatements are written by just giving the new value of K. The parse function knows the difference between SetKStatements and InvokeStatements, because SetKStatements parse to a number. (Note that it would therefore be unwise to use a number as the name of an Executable in the tables of a Calc object.)

Program.java

Programs are just sequences of Statements. A Program is executed by executing each of its Statements in turn. In text, a Program is the text of one Statement after another, delimited by either newlines or commas.

User Interface

The UI is organized into relatively independent components which communicate via a shared Calc object. The components learn about the state of calculations from the Calc object, and can find the spaces bound to names by asking the Calc object.

GUI.java pulls together all the UI components into one interface. You may want to start here to get a top-down perspective on the structure of the UI.

Here's one improvement to the UI that I did not get to implement: All the components already get messages that tell them when calculations start and stop. While a calculation is running, most of the UI is useless: only the cancel button should be available. All components should enable/disable their buttons, etc. whenever calculations start and stop.

Layout.java

Layout.java is just a container for the "addComponent" methods. These provide convenient wrapper functions for GridBagLayout and GridBagConstraints which are used heavily by the UI components.

ChuView.java

ChuViews are components for displaying Chu spaces. There have TextFields for displaying the values of nrows, ncols, and K, plus a large TextArea for displaying the matrix itself. In addition, there are utility methods for displaying a given Chu space and for displaying a given message. The "DisplaySpace" method takes a boolean argument that controls whether nrows, ncols, and K get displayed. (see ChuEdit.java for an example of not displaying these fields). ChuView also keeps a pointer to the last space displayed, and performs no update if the space has not changed. Note that this optimization depends on the fact that Chu spaces are immutable.

ChuSource.java

ChuSource components appear on the left side of the UI, where they provide arguments for unary and binary operations. The "choice" component gives the user a selection of the names of Chu space values. The "view" component (a ChuView) displays the currently selected space.

Whenever there is reason to believe that the space to display has changed, the right binding for the current name is found using "lookupChu". Whenever there is reason to believe that the set of names bound to values may have changed, then the new set of value names is found using "rvalueIdentifiers". The ChuSource learns about the end of a calculation (and the possible need to refresh "choice") by registering an Observer with the Calc object.

ChuTarget.java

A ChuTarget component appears on the right side of the UI, where it provides a place to store the result of a unary or binary operation. ChuTarget works a lot like ChuSource, only with a little more complexity because of newVariableMode. The "choice" contains all the names of variables (provided by "lvalueIdentifiers") plus the entry "NEW". If the user selects "NEW", then the ChuTarget enters newVariableMode.

In this mode, the name of the current space is whatever the user types in the TextField "newVariableField". If the user hits return in this TextField, then the new name is bound to undef. If the user hits an operation button while the target is in newVariableMode, then the result ends up in a new variable with the name the user typed. In both cases, the new name has to be cleared using "newVariableNameOK" from Calc.java to make sure it does not conflict with an existing name of a variable or constant.

ChuEdit.java

A ChuEdit component is used to view and modify the bindings of variables. (Note that Chu spaces themselves are never modified: a binding gets modified by replacing one space with another.) It is difficult to give a meaning to editing the conformed value of a space, so ChuEdit operates on unconformed values. Thus it uses "lookupVariable" (not "lookupChu") to retrieve values from a Calc object.

The user may take any existing value of a variable as a starting point for editing. A click on the parseButton or a return in any of the nrows, ncols, or K fields triggers an attempt to parse the contents of the left ChuView using the parse constructor in Chu.java. This constructor crops or pads the matrix to fit the given number of rows and columns, and throws an exception if the matrix conflicts with the given value of K. These behaviors are sometimes a useful feature, but it can be very annoying if it is not intended by the user. Therefore, by default spaces get displayed with the nrows, ncols, and K fields left blank: The user can fill them in if desired.

If the parse succeeds, then the resulting space is stored and displayed in the right ChuView. If the result is not what the user desired, no harm is done, because no bindings have yet changed. If the new space is correct, then it may be bound to any variable by using the storeButton.

ScriptPanel.java

A ScriptPanel component is mostly a large TextArea for displaying some text from the scripting language. If any calculation succeeds, then its text gets appended to the text in the script area, so the user can see what has happened. If the user selects an area of text and hits the execButton, then that piece of text gets sent to Calc.java's method "calculate". Thus the user can re-execute a long string previous calculations by highlighting them and hitting the execButton. In addition, the TextArea is editable, so the user can also type any valid piece of scripting language, highlight it, and execute it.

GUI.java

GUI.java puts all the UI components together into one interface. The four main regions in the GUI are arranged vertically. From top to bottom, they are: a status panel, a context panel, a main panel, and a card panel. The status and context panels should be factored out as separate classes, but simply ran out of time.

The status panel has a TextField that reports the current state of the calculator, and a button for canceling a calculation in progress. The context panel displays the current standardization and K value, and allows the user to modify this data.

User clicks on the standardization checkbox cause a "Big" or "little" to be sent to the Calc objext and run. In general, user actions cause pieces of scripting language to be generated, sent to the Calc object, and run. After a calculation is finished, the context panel reloads the values of K and standardization from the Calc object. In general, all UI components resychronize themselves to the Calc object after a calculation finishes.

The most important panel in the UI is the main panel, which has two ChuSources and a ChuTarget, plus columns of buttons for unary and binary operations. Whenever one of these buttons is pressed, the names of the source(s), target, and operation are used to build an AssignStatement. This Statement is then sent to the Calc object, parsed and evaluated.

The bottom panel in the UI is the card panel. This panel is just a trick to save on-screen space. A ScriptPanel and a ChuEdit share the same region of the interface. The user can switch back and forth between them by hitting the associated CheckBoxes.