The Goal of this step is to construct a factbase from the diagrams.

I use my grasem tool for this step.

I use SWI PROLOG (swipl) for creating queries, hence, target its syntax using grasem.

Grasem Overview

Grasem combines Ohm-JS grammar specification and glue semantic specifications in one file.

Grasem simply glues a grammar and a glue spec together into one input file.  It splits the two specifications apart, runs the glue transpiler then glues it into a JS file along with a grammar to produce a single JS program.

[This could be done by a shell script (e.g. by using /usr/bin/split)]

Why combine both parts?

Why keep the parts separated?


Ohm-JS creates parsers from grammar specifications.

Ohm-JS parsers use JS as the toolbox language.


The glue tool supplies JS code to output code depending on matching done by the grammar.

Glue allows the programmer to specify an output action for every rule in the grammar.  Glue uses JS back-tick string notation (`...${x}…`).

The combination of a grammar plus simple string outputting is curiously powerful.

This tool — the grasem tool — is used to transpile a .opml file into a PROLOG factbase that can be queried.

.SVG files could be used instead of .opml files.  I choose to use .opml for this — simple — example only because I have ready access to a non-svg drawing tool (draw.io) plus an .opml editor.

Editing .SVG is not the primary purpose of this essay.  I need to convince you — the reader — that this process is straight-forward and that it can be easily automated.  I believe that performing the transpilation steps manually — and showing the intermediate steps — will be a convincing argument for the simplicity of this technique.


To show this set of steps, I simply sight-read the diagram and transcribed it into a tree-view.  

The tree-view tool, that I use,[1] can export the tree in .opml format.  

From that point forward, we can apply automation.  I happen to use Ohm-JS and PROLOG and UNIX® shell scripts, but, other technologies (like other parser tools, miniKanren, etc.) could be used.

As an example of converting a diagram, let us look at the top-level diagram in this example…


Fig. 1 Top Level Diagram For Creating a Runnable


Fig. 2 Tagged Top Level Diagram For Creating a Runnable

In this diagram, we see

For convenience in this example, we've tagged most of the above with single-letter tags a-m.  We didn't bother to tag the largest, gray rectangle.

A true drawing editor would assign an (x,y) point to the position of each of these graphical items.  For convenience, we've omitted this information and eye-ball the contains relationship directly (this is easier for human manual input than trying to calculate and specify the (x,y) of every item).  A fully automated transpiler would use the (x,y) coordinates of all items to infer contains and intersects relationships.  I have found it useful to generate a bounding box (left/top/right/bottom) for each graphical item before inferring containment and intersection relationships.

Likewise, for human input, it was easier to directly assign text items to the various objects.  A fully automated transpiler would infer text containment from the (x,y) coordinates of the text items and the various graphical items.  The actual text items are seen in the non-tagged diagram Fig. 1.

We start sight-reading by creating an item in the tree view corresponding to each graphical item on the diagram:


[We have left out the text for the top level rounded rect.  This is probably a typo bug.  We'll fix it later.]

Then, for each item in the diagram, we insert attributes into the tree view.  

For example, "cicle a" has 3 attributes:

Note that stroke-width=3 will become significant when we infer information.  A Port with stroke-width=3 will be considered to be an implicit Port — we do not draw connection lines to/from implicit Ports (and we let dynamic inheritance make the connections).

OPML2FB Source Code

The opml2fb.grasem file contains

For example, 

<outline text="circle “a”">

<outline text="color=green" />

<outline text="stroke-width=3" />

<outline text="text=“my Composite Template”" />

is converted to the facts:

circle(id1, "a").

color(id1, green).

strokeWidth(id1, 3).

str(tid2, "my Composite Template").

text(id1, tid2).

The grammar rules involved in this conversion are:

  circleObject = "text=\"circle " stringTail

  colorAttr = "text=\"color=" stringTail

  strokeWidthAttr = "text=\"stroke-width=" stringTail

  textAttr = "text=\"text=" stringTail

[The pattern matches are tortuous due to the actual structure of the <outline…> input.]

and the outputting rules are:

  circleObject [teqc str] = [[circle(${scopeGet ("gobject")}, ${str}).]]

  colorAttr [teqc str] = [[color(${scopeGet ("gobject")}, ${str}).]]

  strokeWidthAttr [teqc str] = [[strokeWidth(${scopeGet ("gobject")}, ${str}).]]

  textAttr [teqc str] =


    [[str(${scopeGet ("tobject")}, ${changeUnicodeQuotes (str)}).\ntext(${scopeGet ("gobject")}, ${scopeGet ("tobject")}).]]

The first grammar rule

  circleObject = "text=\"circle " stringTail

says that the grammar matches  'text="circle ' followed by a stringTail match

  stringTail = notDQ* dq

After such matches occur, the glue rules are invoked with matches as parameters to the rules (teqc and str, in this case):

  circleObject [teqc str] = [[circle(${scopeGet ("gobject")}, ${str}).]]

The glue rule says that when a match of a circleObject happens, output it as


where xxx is the ID of the most-recent object and yyy is the stringTail.

We want to create a new ID for every graphical object and we want attributes to reference the IDs.  We create new IDs by calling scopeAdd("gobject",…) and we reference such IDs by calling scopeGet("gobject").

New gobject ID's are created during the tree-walk, and, various sub-rules reference and output this value during the tree-walk.

We use scopeAdd("…",value) to insert values into the dynamic scope and scopeGet("…") to fetch the most recent value.

The opml2fb.grasem file is included below:


  OPML = xmlHeader opmlHeader head body Outline endBody endOPML

  opmlHeader = "<opml" opmlHeaderChar* ">" newline

  xmlHeader = "<?xml" xmlHeaderChar* "?>" newline

  head = "<head>" headChar* "</head>" newline

  body = "<body>" newline

  endBody = "</body>" newline

  endOPML = "</opml>" (newline | end)

  Outline = OutlineNoContent | OutlineWithContent

  OutlineNoContent = "<outline" item "/>"

  OutlineWithContent = "<outline" item ">" Outline* "</outline>"

  item = noise | graphicalObject | attribute 

  graphicalObject = lineObject | arrowObject | circleObject | compObject | rectObject | cylinderObject

  attribute = colorAttr | strokeWidthAttr | textAttr | genericAttr


  noise = "text=\"lines" stringTail

  circleObject = "text=\"circle " stringTail

  compObject = "text=\"comp " stringTail

  rectObject = "text=\"rect " stringTail

  cylinderObject = "text=\"cyl " stringTail

  lineObject = "text=\"line " stringTail

  arrowObject = "text=\"arrow " stringTail

  colorAttr = "text=\"color=" stringTail

  strokeWidthAttr = "text=\"stroke-width=" stringTail

  textAttr = "text=\"text=" stringTail

  genericAttr = "text=" string


  headChar = ~"</head>" any

  opmlHeaderChar = ~">" any

  xmlHeaderChar = ~"?>" any

  newline = "\n"

  string = dq notDQ* dq

  stringTail = notDQ* dq

  dq = "\""

  notDQ = ~"\"" any


  OPML [xmlhdr opmlhdr head body outline endbody endopml] =



  opmlHeader [begin @hdr close nl] = [[]]

  xmlHeader [begin @hdr close nl] = [[]]

  head [begin @hdr close nl] = [[]]

  body [begin nl] = [[]]

  endBody [begin nl] = [[]]

  endOPML [begin nl] = [[]]

  Outline [o] = [[${o}]]

  OutlineNoContent [begin t slashClose] = [[${t}\n]]

  OutlineWithContent [begin t close @o slasho] = [[${t}${o}]]

  headChar [c] = [[${c}]]

  opmlHeaderChar [c] = [[${c}]]

  xmlHeaderChar [c] = [[${c}]]

  newline [c] = [[${c}]]

  item [i] = [[${i}]]

  graphicalObject [o] = [[${o.trim ()}\n]]

  attribute [a] = [[${a.trim ()}\n]]

  circleObject [teqc str] = [[circle(${scopeGet ("gobject")}, ${str}).]]

  compObject [teqc str] = [[comp(${scopeGet ("gobject")}, ${str}).]]

  rectObject [teqc str] = [[rect(${scopeGet ("gobject")}, ${str}).]]

  cylinderObject [teqc str] = [[cylinder(${scopeGet ("gobject")}, ${str}).]]

  lineObject [teqc str] = [[line(${scopeGet ("gobject")}, ${str}).]]

  arrowObject [teqc str] =


arrowBegin(${scopeGet ("gobject")}, ${abegin (str)}).

arrowEnd(${scopeGet ("gobject")}, ${aend (str)}).]]

  colorAttr [teqc str] = [[color(${scopeGet ("gobject")}, ${str}).]]

  strokeWidthAttr [teqc str] = [[strokeWidth(${scopeGet ("gobject")}, ${str}).]]

  textAttr [teqc str] =


    [[str(${scopeGet ("tobject")}, ${changeUnicodeQuotes (str)}).\ntext(${scopeGet ("gobject")}, ${scopeGet ("tobject")}).]]

  genericAttr [teq str] = [[${teq}${str}]]

  string [q1 @cs q2] = [[${q1}${changeUnicodeQuotes (cs)}${q2}]]

  stringTail [@cs q2] = [[${changeUnicodeQuotes (cs)}${q2}]]

  notDQ [c] = [[${c}]]

  dq [c] = [[]]

  noise [a b] = [[]]


The resulting factbase is:

arrowBegin(id40, a).

arrowBegin(id40, b).

arrowBegin(id40, c).

arrowBegin(id40, f).

arrowBegin(id40, g).

arrowBegin(id40, h).

arrowBegin(id40, j).

arrowBegin(id40, l).

arrowBegin(id40, n).

arrowBegin(id40, s).

arrowBegin(id40, v).

arrowBegin(id69, a).

arrowBegin(id69, b).

arrowBegin(id69, c).

arrowBegin(id69, f).

arrowBegin(id69, h).

arrowBegin(id69, l).

arrowBegin(id69, m).

arrowBegin(id69, p).

arrowBegin(id69, s).

arrowEnd(id40, [d]).

arrowEnd(id40, [f]).

arrowEnd(id40, [h]).

arrowEnd(id40, [j]).

arrowEnd(id40, [l]).

arrowEnd(id40, [m]).

arrowEnd(id40, [o]).

arrowEnd(id40, [p]).

arrowEnd(id40, [r]).

arrowEnd(id40, [t]).

arrowEnd(id40, [u]).

arrowEnd(id69, [d]).

arrowEnd(id69, [f]).

arrowEnd(id69, [g]).

arrowEnd(id69, [h]).

arrowEnd(id69, [k]).

arrowEnd(id69, [l,o]).

arrowEnd(id69, [l]).

arrowEnd(id69, [n]).

arrowEnd(id69, [r]).

circle(id1, "a").

circle(id11, "c").

circle(id13, "d").

circle(id3, "b").

circle(id45, "a").

circle(id46, "b").

circle(id47, "c").

circle(id7, "a").

circle(id70, "d").

circle(id73, "g").

circle(id9, "b").

color(id1, green).

color(id11, green).

color(id13, yellow).

color(id16, green).

color(id17, yellow).

color(id22, green).

color(id23, green).

color(id24, yellow).

color(id29, green).

color(id3, green).

color(id30, yellow).

color(id35, green).

color(id36, green).

color(id37, yellow).

color(id45, green).

color(id46, green).

color(id47, green).

color(id50, green).

color(id51, green).

color(id52, yellow).

color(id57, green).

color(id58, green).

color(id59, yellow).

color(id64, green).

color(id65, green).

color(id66, yellow).

color(id7, green).

color(id70, yellow).

color(id73, yellow).

color(id9, green).

comp(id0, "A").

comp(id14, "e").

comp(id20, "g").

comp(id27, "i").

comp(id33, "k").

comp(id43, "e").

comp(id48, "e").

comp(id5, "c").

comp(id55, "g").

comp(id62, "i").

cylinder(id18, "f").

cylinder(id25, "h").

cylinder(id31, "j").

cylinder(id38, "l").

cylinder(id41, "d").

cylinder(id53, "f").

cylinder(id60, "h").

cylinder(id67, "j").

cylinder(id71, "f").

rect(id16, "m").

rect(id17, "n").

rect(id22, "o").

rect(id23, "p").

rect(id24, "q").

rect(id29, "r").

rect(id30, "s").

rect(id35, "t").

rect(id36, "u").

rect(id37, "v").

rect(id50, "k").

rect(id51, "l").

rect(id52, "m").

rect(id57, "n").

rect(id58, "o").

rect(id59, "p").

rect(id64, "q").

rect(id65, "r").

rect(id66, "s").

str(tid10, "my Composite Template").

str(tid12, "child").

str(tid15, "make instance").

str(tid19, "child instance").

str(tid2, "my Composite Template").

str(tid21, "invent name").

str(tid26, "named child instance").

str(tid28, "recursively instantiate").

str(tid32, "filled child instance").

str(tid34, "insert child into children of my runnable").

str(tid39, "my runnable filled in with children").

str(tid4, "my runnable").

str(tid42, "my runnable filled in with children").

str(tid44, "∀ connections of my Composite Template").

str(tid49, "clone connection").

str(tid54, "runnable connection with holes").

str(tid56, "fixup connection").

str(tid6, "∀ children of my Composite Template").

str(tid61, "fixed up connection").

str(tid63, "insert connection into runnable").

str(tid68, "final runnable").

str(tid72, "runnable").

str(tid8, "my runnable").

strokeWidth(id1, 3).

strokeWidth(id3, 1).

strokeWidth(id45, 1).

strokeWidth(id46, 1).

strokeWidth(id47, 1).

strokeWidth(id50, 1).

strokeWidth(id51, 1).

strokeWidth(id52, 1).

strokeWidth(id57, 1).

strokeWidth(id58, 1).

strokeWidth(id59, 1).

strokeWidth(id64, 1).

strokeWidth(id65, 1).

strokeWidth(id66, 1).

strokeWidth(id70, 1).

strokeWidth(id73, 1).

text(id1, tid2).

text(id11, tid12).

text(id14, tid15).

text(id18, tid19).

text(id20, tid21).

text(id25, tid26).

text(id27, tid28).

text(id3, tid4).

text(id31, tid32).

text(id33, tid34).

text(id38, tid39).

text(id41, tid42).

text(id43, tid44).

text(id48, tid49).

text(id5, tid6).

text(id53, tid54).

text(id55, tid56).

text(id60, tid61).

text(id62, tid63).

text(id67, tid68).

text(id7, tid8).

text(id71, tid72).

text(id9, tid10).

Note that PROLOG requires that all facts with the same name be grouped together.  We do this by invoking the UNIX® sort command (see run.bash — https://github.com/guitarvydas/basicdasl/blob/master/pseudo/run.bash)







Ohm-JS (skim or familiarize yourself with Ohm-JS using this essay or Ohm-JS documentation) 


ohm-js editor 











details kill


software development roles


toolbox language



screencasts (esp. PROLOG For Programmers)


[1] Cloud Outliner