Escher A language for connecting technologies using pure metaphors

Debugging and panics

Similarly to other languages like Go, Escher has two mechanisms for debugging programs: panic traces and user-controlled code instrumentation.

Panic traces

There are two ways in which a running Escher program can panic:

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

Instrumentation reflexes

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