PreviousUpNext

15.4.1034  src/lib/std/dot/dot-graph-io-g.pkg

## dot-graph-io-g.pkg

# Compiled by:
#     src/lib/std/standard.lib


# I/O of graphs using the "dot" syntax.
#
# NOTE: the make*info functions should take a "String -> String" dictionary
# and build the node info from that, but this will require wholesale changes.   XXX BUGGO FIXME

# This generic is compiletime expanded by:
#     src/lib/std/dot/dot-graphtree.pkg

                                                                # Winix_Text_File_For_Os__Premicrothread                        is from   src/lib/std/src/io/winix-text-file-for-os--premicrothread.api
                                                                # Dot_Graph_Io                                                  is from   src/lib/std/dot/dot-graph-io.api
                                                                # dot_graphtree_traits                                          is from   src/lib/std/dot/dot-graphtree-traits.pkg

stipulate
    package fil =  file__premicrothread;                        # file__premicrothread                                          is from   src/lib/std/src/posix/file--premicrothread.pkg
herein

    generic package  dot_graph_io_g  (

        package io:  Winix_Text_File_For_Os__Premicrothread
                     where Input_Stream  == fil::Input_Stream
                      also Output_Stream == fil::Output_Stream;

        package g:   Traitful_Graphtree;

        # Functions to make the default graph info:
        #
        make_default_graph_info:  Void -> g::Graph_Info;                # Currently   {. REF dot_graphtree_traits::default_graph_info; }
        make_default_node_info:   Void -> g::Node_Info;         # Currently   {. REF dot_graphtree_traits::default_node_info;  }
        make_default_edge_info:   Void -> g::Edge_Info;         # Currently   {. REF dot_graphtree_traits::default_edge_info;  }
    )
    : (weak) Dot_Graph_Io
    {

        package io = io;
        package g = g;
                                                                # dotgraph_lr_vals_g                                            is from   src/lib/std/dot/dot-graph.grammar
                                                                # dotgraph_lr_vals_g                                            is from   src/lib/std/dot/dot-graph.grammar.sml
                                                                # lr_parser                                                     is from   src/app/yacc/lib/parser2.pkg
        package graph_lr_vals
            = 
            dotgraph_lr_vals_g (

                package token = lr_parser::token;
                package g = g;

                make_default_graph_info = make_default_graph_info;
                make_default_node_info  = make_default_node_info;
                make_default_edge_info  = make_default_edge_info;
            );
                                                                # dotgraph_lex_g                                                is from   src/lib/std/dot/dot-graph.lex
                                                                # dotgraph_lex_g                                                is from   src/lib/std/dot/dot-graph.lex.sml
        package graph_lex
            =
            dotgraph_lex_g (
                #
                package tokens = graph_lr_vals::tokens;
            );
                                                                # make_complete_yacc_parser_with_custom_argument_g              is from   src/app/yacc/lib/make-complete-yacc-parser-with-custom-argument-g.pkg
        package graph_parser
            = 
            make_complete_yacc_parser_with_custom_argument_g (
                #
                package parser_data = graph_lr_vals::parser_data;
                package lex = graph_lex;
                package lr_parser = lr_parser;
            );

        fun read_graph  input_stream
            =
            {   fun complain msg
                    =
                    fil::write (fil::stderr, string::cat ["lexer: ", msg, "\n"]);

                my lexstate:  graph_lex::user_declarations::Lexstate
                    =
                    {
                      line_num      =>  REF 1,
                      stringstart   =>  REF 0,
                      comment_state =>  REF NULL,
                      #
                      charlist => REF [],
                      complain
                    };

                lexer = graph_parser::make_lexer
                            (\\ max_chars_to_read =   fil::read_n (input_stream, max_chars_to_read))
                            lexstate;

                lookahead = 30;

                fun errfn (msg, _, _)
                    = 
                    fil::write (fil::stderr, "Error (line " + (int::to_string *lexstate.line_num) + ": " + msg + ")\n");

                case (#1 (graph_parser::parse (lookahead, lexer, errfn, ())))
                    #
                    THE g =>  g;
                    NULL  =>  {   errfn("Empty graph", 1, 1);
                                  raise exception g::GRAPHTREE_ERROR "Empty graph";
                              };
                esac;
            };

        stipulate

            # This is basically just a curried strcmp:
            #
            #    recognize "foo" "foo" ->  TRUE;
            #    recognize "foo" "bar" ->  FALSE;
            #
            fun recognize s
                =
                {   size_s = size s;

                    cl = explode s;                     # "cl" may be "char_list"

                    \\ (s, i)
                        =
                        {   size_s == (size s)-i
                            and
                            mk (i, cl)

                            where
                                fun mk (i,       []) =>  TRUE;
                                    mk (i, c ! rest) =>  string::get_byte_as_char (s, i) == c and mk (i+1, rest);
                                end;
                            end;
                        };
                };

            rec_edge = recognize "dge";
            rec_node = recognize "ode";
            rec_strict = recognize "rict";
            rec_digraph = recognize "igraph";
            rec_graph = recognize "raph";
            rec_subgraph = recognize "bgraph";

            minlen = 4;

        herein

            # Return TRUE iff 's' is one of these keywords:
            #     edge, node, strict, digraph, graph, subgraph.
            #
            fun is_keyword s
                =
                if (size s < minlen)
                    #
                    FALSE;
                else
                    case (string::get_byte_as_char (s, 0))
                        #
                        'd' =>  rec_digraph (s, 1);
                        'e' =>  rec_edge    (s, 1);
                        'g' =>  rec_graph   (s, 1);
                        'n' =>  rec_node    (s, 1);
                        #
                        's' =>  case (string::get_byte_as_char (s, 1))
                                    #
                                    't' =>  rec_strict   (s, 2);
                                    'u' =>  rec_subgraph (s, 2);
                                     _  =>  FALSE;
                                esac;

                         _  => FALSE;
                    esac;
                fi;     
        end;


        # Convert a string into canonical surface form for use
        # as a value in a foo.dot file "key = value" trait.
        #
        # This typically involves putting a doublequote before
        # and after and backslashing any internal quotes.
        #
        # If the string is a simple identifier ([A-Za-z0-9_]+) or
        # a number ([0-9.]+) it needs no quotes so we return
        # it unchanged, except that if it is string-equal to any
        # of the keywords
        #     edge, node, strict, digraph, graph, subgraph
        # then we still need to wrap the value in quotes to 
        # prevent the lexer from classifying it as a keyword
        # rather than a string:
        #
        #
        fun canon ""
                =>
                "\"\"";

            canon str
                =>
                {   maybe_num
                        =
                        {   c = string::get_byte_as_char (str, 0);
                            #
                            char::is_digit c or (c == '.');
                        };


                    # We return the new string plus a boolean flag recording
                    # whether the string needs to be wrapped in quotes due
                    # to not being a syntactically valid identifier or number:
                    #
                    fun run ([], l, must_quote)
                            =>
                            ('"' ! l, must_quote);

                        run ('"' ! rest, l, must_quote)
                            =>
                            run (rest, '"' ! '\\' ! l, TRUE);

                        run (c ! rest, l, must_quote)
                            =>
                            if (not (char::is_alphanumeric c) and (c != '_'))
                                #
                                run (rest, c ! l, TRUE);

                            elif (maybe_num and not (char::is_digit c) and (c != '.'))

                                run (rest, c ! l, TRUE);
                            else
                                run (rest, c ! l, must_quote);
                            fi;
                    end;

                    # Return the input string unchanged if practical,
                    # otherwise wrapped in double-quotes and with
                    # internal double-quotes backslashed:
                    #
                    case (run (explode str, ['"'], FALSE))
                        #
                        (cl, TRUE ) =>  implode (reverse cl);                           # Must quote it because it contains a blank or such.
                        (cl, FALSE) =>  if (is_keyword str)   implode (reverse cl);             # Must quote it to distinguish it from a keyword.
                                        else                  str;                              # No need to wrap quotes, so return it unchanged.
                                        fi;
                    esac;
                };
        end;

        # Given ("foo", "bar") return "foo = bar".
        # Given ("foo", "x y") return "foo = \"x y\""
        #
        fun make_trait (n, v)
            =
            cat [n, " = ", canon v];

        trait_list_to_string
            =
            list_to_string::list_to_string' { first=>" [", between=>", ", last=>"]", to_string=>make_trait };


        fun write_graph (outs, graph)
            =
            {   write_strings = apply (\\ s = fil::write (outs,s));
                #
                fun tab () = write_strings ["  "];
                fun nl  () = write_strings [";\n"];

                my (graph_type, edge_op)
                    = 
                    case (g::get_trait (g::GRAPH_PART graph) "graph_type")
                        #
                        NULL =>  ("digraph", " -> ");

                        THE gt =>
                            {   g::drop_trait (g::GRAPH_PART graph) "graph_type";

                                case gt
                                    #
                                    "g"   => ("graph",          " -- ");
                                    "sg"  => ("strict graph",   " -- ");
                                    "dg"  => ("digraph",        " -> ");
                                    "sdg" => ("strict digraph", " -> ");
                                     _    => ("digraph",        " -> ");
                                esac;
                            };
                    esac;

                get_proto_node =  g::get_trait  (g::PROTONODE_PART graph);
                get_proto_edge =  g::get_trait  (g::PROTOEDGE_PART graph);

                fun get_diff_attr (chunk, lookup)
                    =
                    {   l =  REF ([]:  List( (String, String) ));
                        #
                        fun check (nv as (n, v))
                            =
                            case (lookup n)
                                #
                                NULL   =>                l :=  nv ! *l;
                                THE v' =>  if (v' != v)  l :=  nv ! *l;  fi;
                            esac;

                        if (g::count_trait chunk == 0)
                            #
                            [ ];
                        else
                            g::trait_apply chunk check;
                            *l;
                        fi;
                    };

                fun get_and_drop (chunk, name)
                    =
                    case (g::get_trait chunk name)
                        #
                        NULL  =>  "";
                        THE v =>  { g::drop_trait chunk name; v; };
                    esac;

                fun write_traits [ ] =>  ();
                    write_traits al  =>  write_strings [ trait_list_to_string al ];
                end;

                fun write_edge e
                      =
                      {   my { head, tail } = g::nodes_of e;

                          tp = get_and_drop (g::EDGE_PART e, "tailport");
                          hp = get_and_drop (g::EDGE_PART e, "headport");

                          tab();

                          write_strings [canon (g::node_name tail), tp, edge_op, canon (g::node_name head), hp];

                          write_traits (get_diff_attr (g::EDGE_PART e, get_proto_edge));

                          nl();
                      };

                fun write_node n
                    =
                    {   tab();
                        write_strings [canon (g::node_name n)];
                        write_traits (get_diff_attr (g::NODE_PART n, get_proto_node));
                        nl();
                    };

                fun write_dictionary (label, chunk)
                    =
                    if (g::count_trait chunk != 0)
                        #
                        tab();
                        write_strings [ label ];
                        write_traits (get_diff_attr (chunk, \\ _ = NULL));
                        nl();
                    fi;

                write_strings [graph_type, " ", canon (g::graph_name graph), "{\n"];

                write_dictionary ("graph", g::GRAPH_PART     graph);
                write_dictionary ("node",  g::PROTONODE_PART graph);
                write_dictionary ("edge",  g::PROTOEDGE_PART graph);

                g::nodes_apply write_node graph;
                g::nodes_apply (\\ n = apply write_edge (reverse (g::out_edges (graph, n)))) graph;

                write_strings ["}\n"];
            };

    };                                  # generic package  dot_graph_io_g
end;

## COPYRIGHT (c) 1994 AT&T Bell Laboratories.
## Subsequent changes by Jeff Prothero Copyright (c) 2010-2015,
## released per terms of SMLNJ-COPYRIGHT.


Comments and suggestions to: bugs@mythryl.org

PreviousUpNext