Similarly to other languages like Go, Escher has two mechanisms for debugging programs: panic traces and user-controlled code instrumentation.
There are two ways in which a running Escher program can panic:
Cognize method, orIn both cases, two types of “traces” will be printed out automatically before the process exits.
One of these traces is the standard Go stack trace. This is useful to pin-point the location in the
Go implementation of a reflex where the panic occurs, in the event of panics occuring in Cognize
methods. The Go stack trace, however, will not reflect the materialization path that lead to the
creation of the problematic reflex. This is reflected by the second type of trace, which we demonstrate
by example.
Consider the following toy Escher program:
{
*Show = "Parent circuit"
m *escher.QuickMaterialize
m:Residue = *Ignore
m:Index = *escher.Index
m:Program = {
*escher.Breakpoint = 1
}
}
This program will first materialize the inner program, which in turn will send the constant 1
to the breakpoint reflex, causing it to panic. In other words, an outer circuit materializes an inner circuit
and subsequnetly a panic occurs in the inner circuit. The goal of the Escher trace is to reflect that.
The following Escher trace will be printed:
BASIS(:)
DIRECTIVE(:) *escher.Breakpoint/*escher.Breakpoint
CIRCUIT() {
0 *escher.Breakpoint
1 1
0: = 1:
}
MATERIALIZE() {
0 *escher.Breakpoint
1 1
0: = 1:
}
BASIS(:Residue :View)
DIRECTIVE(:Residue :View) *escher.Materialize/*escher.Materialize
CIRCUIT(:Index :Program :Residue) {
x *escher.Materialize
y *Fork
:Residue = x:Residue
:Index = y:Index
:Program = y:Program
x:View = y:
}
DIRECTIVE(:Index :Program :Residue) *escher.QuickMaterialize/*escher.QuickMaterialize
CIRCUIT() {
m *escher.QuickMaterialize
0 *Show
1 "Parent circuit"
2 *Ignore
3 *escher.Index
4 {
0 *escher.Breakpoint
1 1
0: = 1:
}
0: = 1:
m:Program = 4:
m:Residue = 2:
m:Index = 3:
}
DIRECTIVE() *tutorial.Debug/*tutorial.Debug
MATERIALIZE() *tutorial.Debug
MAIN()
Escher traces consist of frames, indicated by capital letters. Frames correspond to reflexes (basis or derivative) or directives. They are listed in most-specific to least-specific order: The first frame corresponds to the problematic reflex, whereas the last one corresponds to the main circuit being materialized.
Since every frame corresponds to a reflex that is materialized, a list of valves connected to this reflex is given in brackets next to the frame name. Following the brackets is a frame argument whose meaning depends on the type of frame:
MAIN marks the start of the Escher runtime.
MATERIALIZE frames mark the beginning of materialization. The argument of such frames
describe the program that is being materialized.
DIRECTIVE frames indicate that a directive gate value is being resolved.
The argument equals the source of the directive, followed by the computed fully-qualified
source of directive (in case the directive uses local addressing).
CIRCUIT frames indicate that a program circuit is being materialized. Their
argument equals the circuit source.
BASIS frames indicate that a basis reflex is being materialized.
NOUN frames indicate that a noun reflex is being materialized.
In many lanuages the simplest instrumentation technique is the insertion of “printf”
statements. Escher has its own analog. Given a link in a circuit program, the idea is to print out
the values that flow through that link without otherwise affecting the execution of the program.
This is accomplished with the use of a *Show reflex, which simply lets values
pass through it while printing them on standard error together with the name of the valve they
were received on.
Suppose the following program is to be debugged:
{
source *Source
sink *Sink
source: = sink:
}
We could then add a debug *Show reflex to “eavesdrop” on the link
from source to sink, like so:
{
source *Source
sink *Sink
eve *Show
source: = eve:Source
eve:Sink = sink:
}