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 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).
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
circle(xxx,yyy).
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:
OPML2FB {
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] =
[[${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)
https://guitarvydas.github.io/2021/04/26/Factbases-101.html
https://guitarvydas.github.io/2021/04/11/Grasem.html
https://guitarvydas.github.io/2021/05/09/Software-Components-101-Engine-Part-2-Diagram-to-Text.html
Ohm-JS (skim or familiarize yourself with Ohm-JS using this essay or Ohm-JS documentation)
https://guitarvydas.github.io/2020/12/09/OhmInSmallSteps.html
ohm-js editor
https://guitarvydas.github.io/2021/05/09/Ohm-Editor.html
glue
https://guitarvydas.github.io/2021/04/11/Glue-Tool.html
grasem
https://guitarvydas.github.io/2021/04/11/Grasem.html
isolation
https://guitarvydas.github.io/2021/01/16/Superposition.html
https://guitarvydas.github.io/2021/01/22/superposition.html
https://guitarvydas.github.io/2021/01/24/superposition-2.html
https://guitarvydas.github.io/2020/12/09/Isolation.html
details kill
https://guitarvydas.github.io/2021/03/17/Details-Kill.html
software development roles
https://guitarvydas.github.io/2020/12/10/Software-Development-Roles.html
toolbox language
https://guitarvydas.github.io/2021/03/16/Toolbox-Languages.html
https://guitarvydas.github.io/2021/04/28/Toolbox-Languages-(2).html
screencasts (esp. PROLOG For Programmers)
https://guitarvydas.github.io/2021/04/11/Playlists.html
[1] Cloud Outliner