The Architecture of the Leibniz System
The behavior described above is implemented through a combination of functions performed by the application and the kernel.
Roughly speaking, the application contains the code necessary to translate expressions to and from a form that the kernel
can understand, while code in the
LZCompute package implements all of the necessary pattern matching and calculation tasks.
The application is responsible for parsing mathematical expressions and building an internal tree structure to reflect the
structure of the expression. In addition, the tree structure also contains information about the location of each subexpression.
As the user drags the mouse, the application uses this information to map the current mouse location to the portion of the
expression the mouse is in. When results come back from the kernel, the application translates them from Mathematica form to a form suitable for display.
Most of the interesting behavior in this system is implemented in the
LZCompute package. This is due to a conscious design decision. There were many manipulations and calculations that could have been
performed either in the application itself or by code contained in the package. Whenever the option came up to either hard
code a capability in C++ in the application or as Mathematica code in the package, I chose to implement the operation in the package. This provides two obvious benefits: the code in the
package is easier to modify, and end users with a knowledge of Mathematica can modify the behavior of the system to suit their needs.
Implementing Drag-and-Drop Calculations
When the user clicks and drags inside an expression, Leibniz sends a request to the kernel to evaluate a potential drag-and-drop
operation. It does this by calling the function
LZEvaluateShuffle defined in the
LZCompute package. For example, to evaluate the dragging operation shown in the illustration above, Leibniz would call the following.
LZTarget calls wrapped around parts of the expression. These function calls are used to indicate which part of the expression is being
dragged (the mover) and which part of the expression is the target.
LZEvaluateShuffle returns a list of code numbers corresponding to actions that are possible.
LZEvaluateShuffle had to just return a single response code, life would be pretty simple. We could make a series of definitions like this.
To indicate how
LZEvaluateShuffle should respond to a particular dragging request. In order to return a list of matches, we have to be a little more clever.
This is how
LZEvaluateShuffle is actually implemented.
LZEvaluateShuffle is implemented as a fixed point calculation starting with an empty list and repeatedly applying the
EvalShuffle function. Rather than return a single code number,
EvalShuffle appends its code number to the list only if the list does not already contain that particular code number.
EvalShuffle is defined via a series of calls to the
ES function: each of those calls defines
EvalShuffle for a particular pattern. For example, the
ES rule that matches the drag request shown above is the following.
How does Leibniz know which subexpression to mark as the target? After all, a given element in a mathematical expression is
a member of multiple possible subexpressions. When the user moves the mouse to a new location in an expression, the application
uses the information stored in the parse tree to map the location to a subexpression, typically a leaf in the parse tree.
Leibniz then calls
LZEvaluateShuffle with that subexpression identified as the target. If the result comes back empty, Leibniz moves up to the next largest subexpression
containing the mouse location and calls
LZEvaluateShuffle again with that subexpression identified as the target. This process continues until an action is found or there are no more
possibilities for the target expression. Despite the fact that this process often requires multiple calls to
LZEvaluateShuffle via MathLink, the process is fast enough to work comfortably in real time as the user drags the mouse over the expression.
Once Leibniz gets a list of code numbers back from the kernel, it calls the
LZMessage function to retrieve descriptive strings for each of the potential actions. The descriptive string for the most likely option
gets displayed immediately in the message panel. For example, the
LZMessage entry that corresponds to the
ES entry shown above is this.
As soon as the user releases the mouse button, Leibniz sends a request to evaluate the dragging operation. The
LZShuffle function handles this request. The first parameter to
LZShuffle is the code number for the desired action, and the second parameter is the expression being manipulated with mover and target
identified. For example, the
LZShuffle definition that matches the
ES rule shown above is this.
Copyright © 2002 Wolfram Media, Inc. All rights reserved.[Article Index][Prev Page][Next Page]