# 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 } # 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 "" } # "// 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 && $NF ~ /\w+,$/ { # 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; } 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 %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 }