tame/tamer/build-aux/asg-ontviz.awk

193 lines
5.7 KiB
Awk

# Generate ontology visualization for the ASG
#
# Copyright (C) 2014-2023 Ryan Specialty, LLC.
#
# This file is part of TAME.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# Use the sibling `asg-ontvis` script to invoke this for you with the
# necessary files.
#
# The relationships between objects on the ASG are defined declaratively
# using the `object_rel!` macro.
# They follow this syntax:
#
# object_rel! {
# Source -> {
# tree TargetA,
# tree TargetB,
# cross TargetC,
# }
# }
#
# But edges also support arbitrary code definitions:
#
# object_rel! {
# Source -> {
# tree TargetA {
# fn pre_add_edge(...) {
# // ...
# }
# },
# tree TargetB,
# cross TargetC,
# }
# }
#
# And because of that,
# this script hard-codes the expected level of nesting just to make life
# easier.
#
# This script expects to receive a list of files containing such
# definitions.
# It will output,
# to stdout,
# a DOT definition representing the ASG's ontology,
# which can be used to render a visualization.
#
# To render the output of this script,
# pipe it to e.g. `dot -Tsvg > out.svg`.
#
# This tool is supposed to generate human-readable DOT output;
# please retain that quality in any changes you make.
# This is helpful not only to help the user figure out what output they're
# seeing,
# but is also useful because the DOT output is quite understandable by
# itself without the need to generate a graphical representation,
# depending on the information the user is looking for.
BEGIN {
print "# Representation of the TAMER ASG ontology generated by asg-ontviz."
print "# Generate visualization using e.g. `dot -Tsvg > out.svg`."
print "digraph {"
# Whether we're parsing a `object_rel!` block.
in_block = 0
# The current source relation, if `in_block_subexpr`.
block_src = ""
# The final exit status (0 = success)
exit_code = 0
}
BEGINFILE {
# Whether any object relationships were found in the file.
# This is used in `ENDFILE` to notify the user that something may be
# wrong.
found_rels = 0
}
# Predicates will be reset for each line,
# allowing the remainder of the script to be read more declaratively.
{ in_block = in_block_subexpr = 0 }
# Block predicates for each line of text.
/^object_rel! {$/, /^}$/ { in_block = 1 }
# `Foo -> {` line declares the source of the relation.
# We hard-code the expected depth to simplify parsing.
in_block && /^ \w+ -> \{$/ {
block_src = $1
printf " # `%s` from `%s:%d`\n", block_src, FILENAME, FNR
next
}
# A closing curly brace always means that we've finished with the current
# source relation,
# since we're at the innermost level of nesting.
block_src && /^ }$/ {
block_src = ""
print ""
}
# "// empty" means that the lack of edges is intentional.
block_src && /^ *\/\/ empty$/ {
# Suppress error from the edge check below.
found_rels++
}
# Skip comments.
/^ *\/+/ { next }
# For each target object,
# output a relation.
#
# The DOT format is pretty rigid;
# because we are styling,
# we cannot use curly braces similar to how the relation is defined in
# Rust using the `object_rel!` macro;
# we must independently define each one.
# But that's okay;
# the output is quite legible.
block_src && /^ \w+ \w+(,| \{)$/ {
# Clean up the end of the string _before_ pulling information out of
# fields,
# since the number of fields can vary.
gsub(/(,| \{)$/, "")
# Edge type (cross, tree)
ty = $(NF-1)
# Dashed is visually in-between solid and dotted,
# and `dyn` is either `tree` or `cross`,
# determined at runtime.
# But we may need some other representation if `dotted` is too visually
# sparse and difficult/annoying to see;
# let's see where the ASG visualization ends up first.
attrs = ""
switch (ty) {
case "tree":
attrs="[style=solid,arrowhead=normal]";
break;
case "cross":
attrs="[style=dotted,arrowhead=open]";
break;
case "dyn":
attrs="[style=dashed,arrowhead=normal]";
break;
}
# This may need updating over time as object names in Rust sources
# exceed the fixed-width definition here.
# This output is intended to form a table that is easy to read and
# visually scan.
# For this reason,
# the source object is right-aligned and target is left-aligned,
# so that `Src -> Target` can be easily read regardless of the width
# of the objects involved.
printf " %5s -> %-5s %32s; # %s edge\n", block_src, $NF, attrs, ty
found_rels++
}
ENDFILE {
if (found_rels == 0) {
# Note that this can happen if the macro is changed or the calling
# `asg-ontvis` shell script is not providing the correct
# filenames.
printf "error: no relations found in `%s`\n", FILENAME >"/dev/stderr"
exit_code = 1
}
}
END {
print "}"
exit exit_code
}