Escher A language for connecting technologies using pure metaphors

Syntax and meaning

At heart Escher is a Go package that parses a simple written syntax into a labeled graph data structure, called a circuit. If you view XML as a syntax that represents labeled trees, then Escher would be a syntax that represents labeled graphs.

Circuits

A circuit consists of nodes, called gates, which have a name and a value. Names are strings or integers. Gates have unique names within a circuit. Values are anything representable by the underlying technology, which for our implementation means any Go value, equivalently, interface{}.

Additionally, a circuit has a set of links across pairs of gates. A link has two endpoints, called vectors. Each vector consists of a gate name and a valve name. Vectors do not overlap in the sense that all vectors with the same gate name have unique valve names.

Symbolism

Circuits have a standard visual representation that fully captures the internal structure of the circuit, which consists of the gate names and links and excludes the gate values—the external structure.

To draw a circuit we start with a solid black oval, denoting the circuit's internal name space. White ovals—contained inside the black one and mutually non-overlapping—denote gates.

Links are depicted as white lines that connect the outlines of gate ovals. Link endpoints connecting to the super gate are attached to the outline of the surrounding black oval.

Valve names are written in white within the black oval, next to their respective visual connection point. Connection points where valve names are visually missing correspond to empty-string valves.

The visual space inside the white gate ovals is reserved for the visual symbolic representation of that value, whatever it might be. If that value is primitive (integer, float, complex, string, directive), we just write it out in black text in the center of the oval. If that value is a circuit, we draw the symbolism for that circuit within the white oval recursively, but this time we switch white and black colors everywhere.

In this illustration, the depicted circuit has three valves at the super gate, labeled as “X”, “Y” and “” (the empty string). The source for this circuit is given later below.

Go interface

Within the Go runtime, circuits are represented by a dedicated type Circuit, whose definition is

type Circuit struct {
	Gate map[Name]Value
	Flow map[Name]map[Name]Vector
}

type Vector struct {
	Gate  Name
	Valve Name
}

type Name interface{}

type Value interface{}

Type Name designates string or int. Type Value designates any Go value.

Using the Escher parser is very simple, in three steps:

The following example illustrates this:

package main

import (
	"fmt"
	"github.com/gocircuit/escher/see"
)

func main() {
	src = "alpha { a 123; b 3.14; a: = b:}\n beta { 1, 2, 3, \"abc\" }"
	p := see.NewSrcString(src) // create a parsing object
	for {
		n, v := see.See(p) // parse one circuit at a time
		if v == nil {
			break
		}
		fmt.Printf("%v %v\n", n, v)
	}
}
Note that parsing errors result in panics.

Grammar

A definition starts with a circuit name followed by a circuit description inside brackets. The name is an alpha-numeric identifier. For instance,

alpha {
	…
}

Between the brackets, one can have any number of statements which are of two kinds: gates and links. Statements are separated by new lines, commas or semi-colons.

Comments

Go-style end-of-line comments are allowed everywhere.

alpha {            // circuit definition
	float 1.23 // gate named float with a floating-point value
	beta {}    // gate named beta with an empty circuit value
}

Gates

Gate statements begin on a new line with a gate name identifier, space, and a gate value expression. There are six value types that can be expressed:

The first four correspond to the Go types int, float64, complex128 and string and are expressed using the same syntax. Addresses have a dedicated Go type Address. They represent a sequence of names and are written as dot-separated fully-qualified names. Finally, circuits—whose dedicated Go type is Circuit— can be values of gates as well.

For instance,

alpha {
	directive1 *fully.qualified.Name
	directive2 @fully.qualified.Name
	integral   123
	floating   3.14
	complex    (1-3i)
	quoted     "abcd\n\tefgh"
	backquoted `
		<html>
			<div>abc</div>
		</html>
	`
}

Gate values can be circuits themselves,

alpha {
	beta {
		Hello World
		Foo   "Bar"
	}
}

Series

Gate names can be omitted in circuit definitions, in which case gates are assigned consequtive integral names, starting from zero. We call the resulting circuits series.

alpha {
	*fully.qualified.Name
	@fully.qualified.Name
	123
	3.14
	(1-3i)
	"abcd\n\tefgh"
	`
		<html>
			<div>abc</div>
		</html>
	`
	{
		A 1
		B "C"
	}
}

Circuit links are semantically symmetric. A link is a pair of two vectors, and a vector consists of a gate name and a valve name.

Vectors are written as the gate name, followed by : (the colon sign), followed by the valve name. Links are written as a vector, followed by optional whitespace, followed by = (the equals sign), followed by another optional whitespace and the second vector. For instance,

	and:XAndY = not:X

A few idioms are commonly useful:

Here is a comprehensive example of link definitions:

Nand {
	and *binary.And
	not *binary.Not

	and:X = :X
	and:Y = :Y
	and:XAndY = not:Z
	not:NotZ = :
}

Syntactic sugar

When circuits are used to represent programs—in other words, executable code—it is common to include a gate and then link to its default valve. To reduce verbosity in this case, link definitions support a piece of syntactic sugar.

Either (or both) vectors in a link definition can be substituted for a gate value. This will be expanded into a gate definition with an automatically-generated name and a link to its default gate in sugar-free syntax. For example,

	sum:X = 123
Will be expanded into
	0 123
	sum:Summand = 0:

In another example both sides of the equation are sugared:

	*os.Scanln = *os.Println
This will expand to:
	0 *os.Scanln
	1 *os.Println
	0: = 1: