/// <summary> /// Write the FSA in dot format. /// </summary> /// <param name="fa">the FSA to write</param> /// <param name="faName">the name of the FSA</param> /// <param name="tw">text writer for the output</param> /// <param name="rankdir">the main direction of the arrows</param> /// <param name="fontsize">the size of the font in labels</param> /// <param name="descr">function that describes the labels as strings</param> public static void AutomatonToDot <S>(Func <S, string> descr, IAutomaton <S> fa, string faName, System.IO.TextWriter tw, RANKDIR rankdir, int fontsize, bool showName) { ITransducer <S> faf = fa as ITransducer <S>; Func <S, bool> isfinal = lab => { return(faf == null ? false : faf.IsFinalRule(lab)); }; List <Move <S> > epsilonmoves = new List <Move <S> >(); Dictionary <Pair <int, int>, string> nonepsilonMoves = new Dictionary <Pair <int, int>, string>(); Dictionary <int, string> finalLabels = new Dictionary <int, string>(); foreach (var move in fa.GetMoves()) { if (move.IsEpsilon) { epsilonmoves.Add(move); } else if (isfinal(move.Label)) { string conLab = descr(move.Label); if (!string.IsNullOrEmpty(conLab)) { string lab; if (finalLabels.TryGetValue(move.SourceState, out lab)) { lab = lab + ", " + conLab; } else { lab = conLab; } finalLabels[move.SourceState] = lab; } } else { var key = new Pair <int, int>(move.SourceState, move.TargetState); string lab; if (nonepsilonMoves.TryGetValue(key, out lab)) { lab = lab + ", " + descr(move.Label); } else { lab = descr(move.Label); } nonepsilonMoves[key] = lab; } } tw.WriteLine("digraph \"" + faName + "\" {"); tw.WriteLine(string.Format("rankdir={0}; fontsize={1};", rankdir.ToString(), fontsize)); tw.WriteLine(); tw.WriteLine("//Initial state"); tw.WriteLine(string.Format("preInit [style = filled, shape = plaintext, color = {1}, fillcolor = white, label = \"{0}\"]", (showName ? faName + ": " : " "), (showName ? "black" : "white"))); tw.WriteLine("//Final states"); foreach (int state in fa.GetStates()) { if (fa.IsFinalState(state) && !finalLabels.ContainsKey(state)) { tw.WriteLine(string.Format("{1} [style = filled, shape = circle, fillcolor = white, fontsize = {0}, peripheries=2]", fontsize, state)); } if (fa.IsFinalState(state) && finalLabels.ContainsKey(state)) { tw.WriteLine(string.Format("{1} [style = filled, shape = circle, fillcolor = white, fontsize = {0}]", fontsize, state)); tw.WriteLine(string.Format("f{0} [style = filled, shape = box, fillcolor = white, label=\"\", peripheries=2]", state)); } } tw.WriteLine(); tw.WriteLine("//Other states"); foreach (int state in fa.GetStates()) { if (!fa.IsFinalState(state)) { tw.WriteLine(string.Format("{1} [style = filled, shape = circle, fillcolor = white, fontsize = {0}]", fontsize, state)); } } tw.WriteLine(); tw.WriteLine("//Transitions"); tw.WriteLine(string.Format("preInit -> {0}", fa.InitialState)); foreach (Move <S> t in fa.GetMoves()) { if (!isfinal(t.Label)) { tw.WriteLine(string.Format("{0} -> {1} [label = \"{2}\"{3}, fontsize = {4} ];", t.SourceState, t.TargetState, t.IsEpsilon ? "()" : descr(t.Label).Replace(@"\n", @"\x0A"), t.IsEpsilon ? "" : "", fontsize)); } else if (finalLabels.ContainsKey(t.SourceState)) { tw.WriteLine(string.Format("{0} -> {1} [label = \"{2}\", fontsize = {3} ];", t.SourceState, "f" + t.TargetState, finalLabels[t.SourceState].Replace(@"\n", @"\x0A"), fontsize)); } } tw.WriteLine("}"); }
/// <summary> /// Write the automaton in dgml format. /// </summary> /// <param name="fa">the automaton to write</param> /// <param name="tw">text writer for the output</param> public static void AutomatonToDgml <S>(int k, IAutomaton <S> fa, string name, System.IO.TextWriter tw, Func <S, string> describeS = null) { ITransducer <S> faf = fa as ITransducer <S>; bool isTransducer = (faf != null); Func <S, bool> isfinal = lab => { return(isTransducer ? faf.IsFinalRule(lab) : false); }; var finalMoves = new Dictionary <int, List <S> >(); var nonFinalMoves = new Dictionary <Tuple <int, int>, List <S> >(); var epsilonmoves = new List <Move <S> >(); var nonEpsilonStates = new HashSet <int>(); Func <int, bool> IsEpsilonState = (s => !nonEpsilonStates.Contains(s)); foreach (var move in fa.GetMoves()) { if (move.IsEpsilon) { epsilonmoves.Add(move); } else if (isfinal(move.Label) && !(faf.IsGuardTrue(move.Label) && faf.GetYieldsLength(move.Label) == 0)) { List <S> rules; if (!finalMoves.TryGetValue(move.SourceState, out rules)) { rules = new List <S>(); finalMoves[move.SourceState] = rules; } rules.Add(move.Label); } else if (!isfinal(move.Label)) { nonEpsilonStates.Add(move.SourceState); List <S> rules; var p = new Tuple <int, int>(move.SourceState, move.TargetState); if (!nonFinalMoves.TryGetValue(p, out rules)) { rules = new List <S>(); nonFinalMoves[p] = rules; } rules.Add(move.Label); } } tw.WriteLine("<?xml version=\"1.0\" encoding=\"utf-8\"?>"); tw.WriteLine("<DirectedGraph xmlns=\"http://schemas.microsoft.com/vs/2009/dgml\" ZoomLevel=\"1.5\" GraphDirection=\"TopToBottom\" >"); tw.WriteLine("<Nodes>"); tw.WriteLine("<Node Id=\"init\" Label=\"{0}\" Stroke=\"white\" Background=\"white\"/>", name); foreach (int state in fa.GetStates()) { if (state == fa.InitialState && fa.IsFinalState(state)) { tw.WriteLine("<Node Id=\"{0}\" Label=\"{1}\" Category=\"State\" >", state, fa.DescribeState(state)); if (!finalMoves.ContainsKey(state)) { //if (IsEpsilonState(state)) // tw.WriteLine("<Category Ref=\"EpsilonState\" />"); //else tw.WriteLine("<Category Ref=\"FinalState\" />"); } //tw.WriteLine("<Category Ref=\"InitialState\" />"); tw.WriteLine("</Node>"); if (finalMoves.ContainsKey(state)) { tw.WriteLine("<Node Id=\"f{0}\" Label=\" \" Category=\"State\" >", state); tw.WriteLine("<Category Ref=\"FinalState\" />"); tw.WriteLine("<Category Ref=\"SinkState\" />"); tw.WriteLine("</Node>"); } } else if (state == fa.InitialState) { tw.WriteLine("<Node Id=\"{0}\" Label=\"{1}\" Category=\"State\" >", state, fa.DescribeState(state)); tw.WriteLine("<Category Ref=\"InitialState\" />"); tw.WriteLine("</Node>"); } else if (fa.IsFinalState(state)) { tw.WriteLine("<Node Id=\"{0}\" Label=\"{1}\" Category=\"State\" >", state, fa.DescribeState(state)); if (!finalMoves.ContainsKey(state)) { //if (IsEpsilonState(state)) // tw.WriteLine("<Category Ref=\"EpsilonState\" />"); //else tw.WriteLine("<Category Ref=\"FinalState\" />"); } tw.WriteLine("</Node>"); if (finalMoves.ContainsKey(state)) { tw.WriteLine("<Node Id=\"f{0}\" Label=\" \" Category=\"State\" >", state); tw.WriteLine("<Category Ref=\"FinalState\" />"); tw.WriteLine("<Category Ref=\"SinkState\" />"); tw.WriteLine("</Node>"); } } else { tw.WriteLine("<Node Id=\"{0}\" Label=\"{1}\" Category=\"State\" />", state, fa.DescribeState(state)); } } tw.WriteLine("</Nodes>"); tw.WriteLine("<Links>"); tw.WriteLine("<Link Source=\"init\" Target=\"{0}\" Label=\"{1}\" Category=\"StartTransition\" />", fa.InitialState, fa.DescribeStartLabel()); foreach (var move in epsilonmoves) { tw.WriteLine("<Link Source=\"{0}\" Target=\"{1}\" Category=\"EpsilonTransition\" />", move.SourceState, move.TargetState); } foreach (var move in nonFinalMoves) { tw.WriteLine(GetNonFinalRuleInfo(k, fa, faf, move.Key.Item1, move.Key.Item2, move.Value, describeS)); } foreach (var move in finalMoves) { tw.WriteLine(GetFinalRuleInfo(k, faf, move.Key, move.Value)); } tw.WriteLine("</Links>"); WriteCategoriesAndStyles(tw); tw.WriteLine("</DirectedGraph>"); }
/// <summary> /// Write the automaton in dgml format into the textwriter. /// </summary> public void Write <S>(IAutomaton <S> fa) { var nonEpsilonMoves = new Dictionary <(int, int), List <S> >(); var epsilonmoves = new List <Move <S> >(); var nonEpsilonStates = new HashSet <int>(); foreach (Move <S> move in fa.GetMoves()) { if (move.IsEpsilon) { epsilonmoves.Add(move); } else { nonEpsilonStates.Add(move.SourceState); var p = (move.SourceState, move.TargetState); if (!nonEpsilonMoves.TryGetValue(p, out List <S>?rules)) { rules = new List <S>(); nonEpsilonMoves[p] = rules; } Debug.Assert(move.Label is not null); rules.Add(move.Label); } } _tw.WriteLine("<?xml version=\"1.0\" encoding=\"utf-8\"?>"); _tw.WriteLine("<DirectedGraph xmlns=\"http://schemas.microsoft.com/vs/2009/dgml\" ZoomLevel=\"1.5\" GraphDirection=\"TopToBottom\" >"); _tw.WriteLine("<Nodes>"); _tw.WriteLine("<Node Id=\"dfa\" Label=\" \" Group=\"Collapsed\" Category=\"DFA\" DFAInfo=\"{0}\" />", GetDFAInfo(fa)); _tw.WriteLine("<Node Id=\"dfainfo\" Category=\"DFAInfo\" Label=\"{0}\"/>", GetDFAInfo(fa)); if (_onlyDFAinfo) { _tw.WriteLine("</Nodes>"); } else { foreach (int state in fa.GetStates()) { _tw.WriteLine("<Node Id=\"{0}\" Label=\"{0}\" Category=\"State\" Group=\"{1}\" StateInfo=\"{2}\">", state, _hideStateInfo ? "Collapsed" : "Expanded", GetStateInfo(fa, state)); if (state == fa.InitialState) { _tw.WriteLine("<Category Ref=\"InitialState\" />"); } if (fa.IsFinalState(state)) { _tw.WriteLine("<Category Ref=\"FinalState\" />"); } _tw.WriteLine("</Node>"); _tw.WriteLine("<Node Id=\"{0}info\" Label=\"{1}\" Category=\"StateInfo\"/>", state, GetStateInfo(fa, state)); } _tw.WriteLine("</Nodes>"); _tw.WriteLine("<Links>"); _tw.WriteLine("<Link Source=\"dfa\" Target=\"{0}\" Label=\"{1}\" Category=\"StartTransition\" />", fa.InitialState, fa.DescribeStartLabel()); _tw.WriteLine("<Link Source=\"dfa\" Target=\"dfainfo\" Label=\"\" Category=\"Contains\" />"); foreach (Move <S> move in epsilonmoves) { _tw.WriteLine("<Link Source=\"{0}\" Target=\"{1}\" Category=\"EpsilonTransition\" />", move.SourceState, move.TargetState); } foreach (KeyValuePair <(int, int), List <S> > move in nonEpsilonMoves) { _tw.WriteLine(GetNonFinalRuleInfo(fa, move.Key.Item1, move.Key.Item2, move.Value)); } foreach (int state in fa.GetStates()) { _tw.WriteLine("<Link Source=\"{0}\" Target=\"{0}info\" Category=\"Contains\" />", state); } _tw.WriteLine("</Links>"); WriteCategoriesAndStyles(); } _tw.WriteLine("</DirectedGraph>"); }
/// <summary> /// Write the FSA in dot format. /// </summary> /// <param name="fa">the FSA to write</param> /// <param name="faName">the name of the FSA</param> /// <param name="tw">text writer for the output</param> /// <param name="rankdir">the main direction of the arrows</param> /// <param name="fontsize">the size of the font in labels</param> /// <param name="descr">function that describes the labels as strings</param> public static void AutomatonToDot <S>(Func <S, string> descr, IAutomaton <S> fa, string faName, System.IO.TextWriter tw, bool showName, Config config) { // TODO: find why is this here ITransducer <S> faf = fa as ITransducer <S>; // TODO: this is ineffective, hoist the faf == null check before the delegate assignment Func <S, bool> isfinal = lab => { return(faf == null ? false : faf.IsFinalRule(lab)); }; // TODO: this block is mostly unused! clean up! // only finalLabels are used List <Move <S> > epsilonmoves = new List <Move <S> >(); Dictionary <Tuple <int, int>, string> nonepsilonMoves = new Dictionary <Tuple <int, int>, string>(); Dictionary <int, string> finalLabels = new Dictionary <int, string>(); foreach (var move in fa.GetMoves()) { if (move.IsEpsilon) { epsilonmoves.Add(move); } else if (isfinal(move.Label)) { string conLab = descr(move.Label); if (!string.IsNullOrEmpty(conLab)) { string lab; if (finalLabels.TryGetValue(move.SourceState, out lab)) { lab = lab + ", " + conLab; } else { lab = conLab; } finalLabels[move.SourceState] = lab; } } else { var key = new Tuple <int, int>(move.SourceState, move.TargetState); string lab; if (nonepsilonMoves.TryGetValue(key, out lab)) { lab = lab + ", " + descr(move.Label); } else { lab = descr(move.Label); } nonepsilonMoves[key] = lab; } } string sanitize(string input) { return (input .Replace("\\", "\\\\") .Replace("\"", "\\\"") //.Replace("<", "\\<") //.Replace(">", "\\>") ); } string openLabelTag = config.htmlLabels ? "<" : "\""; string closeLabelTag = config.htmlLabels ? ">" : "\""; tw.WriteLine("digraph \"" + faName + "\" {"); tw.WriteLine(string.Format("rankdir={0}; fontsize={1};", config.rankdir.ToString(), config.fontsize)); tw.WriteLine(); tw.WriteLine("//Defaults"); tw.WriteLine(string.Format($"node [style = filled, shape = " + config.shape.ToString() + ", fillcolor = white, fontsize = {0}, margin = 0.01]", config.fontsize)); tw.WriteLine(string.Format("edge [ fontsize = {0} ]", config.fontsize)); tw.WriteLine(); tw.WriteLine("//Initial state format"); { showName = showName && !string.IsNullOrEmpty(faName); tw.WriteLine(string.Format("preInit [shape = plaintext, color = {1}, label = \"{0}\"]", (showName ? faName + ": " : ""), (showName ? "black" : "white"))); } tw.WriteLine(); tw.WriteLine("// All other states format"); foreach (int state in fa.GetStates()) { if (fa.IsFinalState(state) && !finalLabels.ContainsKey(state)) { tw.WriteLine(string.Format("{0} [peripheries=2]", state)); } if (fa.IsFinalState(state) && finalLabels.ContainsKey(state)) { tw.WriteLine(string.Format("{0} []", state)); tw.WriteLine(string.Format("f{0} [shape = box, label=\"\", peripheries=2]", state)); } tw.WriteLine(string.Format("{0} [label = " + openLabelTag + "{1}" + closeLabelTag + "]", state, sanitize(fa.DescribeState(state)))); } tw.WriteLine(); tw.WriteLine("//Transitions"); tw.WriteLine(string.Format("preInit -> {0}", fa.InitialState)); foreach (Move <S> t in fa.GetMoves()) { if (!isfinal(t.Label)) { tw.WriteLine(string.Format("{0} -> {1} [label = " + openLabelTag + "{2}" + closeLabelTag + (t.IsEpsilon ? ", style=dashed" : "") + "];", t.SourceState, t.TargetState, t.IsEpsilon ? "ε" : sanitize(descr(t.Label)) )); } else if (finalLabels.ContainsKey(t.SourceState)) { tw.WriteLine(string.Format("{0} -> {1} [label = " + openLabelTag + "{2}" + closeLabelTag + "];", t.SourceState, "f" + t.TargetState, sanitize(finalLabels[t.SourceState]) )); } } tw.WriteLine("}"); }