From f733a85597f6a94cadf446b105e36efb4d3a891b Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Fri, 10 Mar 2023 10:30:54 -0500 Subject: [PATCH] tamer: tools/asg-ontviz: ASG ontology visualization This parses the declarative `object_rel!` definitions from the Rust sources and produces a DOT representation of the ontology of the graph, which can then be rendered using Graphviz. This does not yet introduce it into the build; it ought to be run as part of `make check` (without rendering with Graphviz) to ensure that we catch breaking changes, and `make html` ought to integrate it into the documentation, perhaps as part of `asg::graph` or `asg::graph::object`. DEV-13708 --- tamer/tools/asg-ontviz | 20 +++++ tamer/tools/asg-ontviz.awk | 150 +++++++++++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100755 tamer/tools/asg-ontviz create mode 100644 tamer/tools/asg-ontviz.awk diff --git a/tamer/tools/asg-ontviz b/tamer/tools/asg-ontviz new file mode 100755 index 00000000..34aa7f4e --- /dev/null +++ b/tamer/tools/asg-ontviz @@ -0,0 +1,20 @@ +#!/bin/bash +# Generate DOT representation of TAMER's ASG ontology +# +# See sibling `asg-ontviz.awk` for more information; +# this is just a thin wrapper around it that invokes it with a list of +# source files. + +cd "$(dirname "$0")" + +# We explicitly filter files in this directory to ensure that the expected +# ontological definitions (`object_rel!`) are actually present, +# because otherwise they were moved and we need to update the path. +# This is enforced by the script. +# +# So, to be clear: +# do _not_ do something like `grep -rl 'object_rel!' ../src/asg`. +find ../src/asg/graph/object/ -name '*.rs' \ + -a \! -name 'rel.rs' \ + -a \! -name 'test.rs' \ + | xargs awk -f asg-ontviz.awk diff --git a/tamer/tools/asg-ontviz.awk b/tamer/tools/asg-ontviz.awk new file mode 100644 index 00000000..289fc8a3 --- /dev/null +++ b/tamer/tools/asg-ontviz.awk @@ -0,0 +1,150 @@ +# 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 . + +# 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, +# } +# } +# +# 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 +} + +# Skip comments. +/^ *\/+/ { next } + +# 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. +in_block && /->/ { + 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 "" +} + +# 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 && $NF ~ /\w+,$/ { + # Edge type (cross, tree) + ty = $(NF-1) + + attrs = "" + if (ty == "cross") { + attrs="[style=dashed]" + } + + gsub(/,$/, "") + + # 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 %14s; # %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 +}