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: }