public override string ToString() => PrevCharKind == 0 ? Node.ToString() : $"({CharKind.DescribePrev(PrevCharKind)},{Node})";
public override void SaveDGML(TextWriter writer, int maxLabelLength) { lock (this) { if (maxLabelLength < 0) { maxLabelLength = int.MaxValue; } Dictionary <(int Source, int Target), (TSet Rule, List <int> NfaTargets)> transitions = GatherTransitions(this); writer.WriteLine("<?xml version=\"1.0\" encoding=\"utf-8\"?>"); writer.WriteLine("<DirectedGraph xmlns=\"http://schemas.microsoft.com/vs/2009/dgml\" ZoomLevel=\"1.5\" GraphDirection=\"TopToBottom\" >"); writer.WriteLine(" <Nodes>"); writer.WriteLine(" <Node Id=\"dfa\" Label=\" \" Group=\"Collapsed\" Category=\"DFA\" DFAInfo=\"{0}\" />", FormatInfo(this, transitions.Count)); writer.WriteLine(" <Node Id=\"dfainfo\" Category=\"DFAInfo\" Label=\"{0}\"/>", FormatInfo(this, transitions.Count)); foreach (MatchingState <TSet> state in _stateCache.Values) { string info = CharKind.DescribePrev(state.PrevCharKind); string deriv = WebUtility.HtmlEncode(state.Node.ToString()); string nodeDgmlView = $"{(info == string.Empty ? info : $"Previous: {info} ")}{(deriv == string.Empty ? "()" : deriv)}"; writer.WriteLine(" <Node Id=\"{0}\" Label=\"{0}\" Category=\"State\" Group=\"Collapsed\" StateInfo=\"{1}\">", state.Id, nodeDgmlView); if (_stateFlagsArray[state.Id].IsInitial()) { writer.WriteLine(" <Category Ref=\"InitialState\" />"); } if (state.Node.CanBeNullable) { writer.WriteLine(" <Category Ref=\"FinalState\" />"); } writer.WriteLine(" </Node>"); writer.WriteLine(" <Node Id=\"{0}info\" Label=\"{1}\" Category=\"StateInfo\"/>", state.Id, nodeDgmlView); } writer.WriteLine(" </Nodes>"); writer.WriteLine(" <Links>"); foreach (MatchingState <TSet> initialState in GetInitialStates(this)) { writer.WriteLine(" <Link Source=\"dfa\" Target=\"{0}\" Label=\"\" Category=\"StartTransition\" />", initialState.Id); } writer.WriteLine(" <Link Source=\"dfa\" Target=\"dfainfo\" Label=\"\" Category=\"Contains\" />"); foreach (KeyValuePair <(int Source, int Target), (TSet Rule, List <int> NfaTargets)> transition in transitions) { string label = DescribeLabel(transition.Value.Rule, _builder); string info = ""; if (label.Length > maxLabelLength) { info = $"FullLabel = \"{label}\" "; label = string.Concat(label.AsSpan(0, maxLabelLength), ".."); } writer.WriteLine($" <Link Source=\"{transition.Key.Source}\" Target=\"{transition.Key.Target}\" Label=\"{label}\" Category=\"NonEpsilonTransition\" {info}/>"); // Render NFA transitions as labelless "epsilon" transitions (i.e. ones that don't consume a character) // from the target of the DFA transition. foreach (int nfaTarget in transition.Value.NfaTargets) { writer.WriteLine($" <Link Source=\"{transition.Key.Target}\" Target=\"{nfaTarget}\" Category=\"EpsilonTransition\"/>"); } } foreach (MatchingState <TSet> state in _stateCache.Values) { writer.WriteLine(" <Link Source=\"{0}\" Target=\"{0}info\" Category=\"Contains\" />", state.Id); } writer.WriteLine(" </Links>"); writer.WriteLine(" <Categories>"); writer.WriteLine(" <Category Id=\"DFA\" Label=\"DFA\" IsTag=\"True\" />"); writer.WriteLine(" <Category Id=\"EpsilonTransition\" Label=\"Epsilon transition\" IsTag=\"True\" />"); writer.WriteLine(" <Category Id=\"StartTransition\" Label=\"Initial transition\" IsTag=\"True\" />"); writer.WriteLine(" <Category Id=\"FinalLabel\" Label=\"Final transition\" IsTag=\"True\" />"); writer.WriteLine(" <Category Id=\"FinalState\" Label=\"Final\" IsTag=\"True\" />"); writer.WriteLine(" <Category Id=\"SinkState\" Label=\"Sink state\" IsTag=\"True\" />"); writer.WriteLine(" <Category Id=\"EpsilonState\" Label=\"Epsilon state\" IsTag=\"True\" />"); writer.WriteLine(" <Category Id=\"InitialState\" Label=\"Initial\" IsTag=\"True\" />"); writer.WriteLine(" <Category Id=\"NonEpsilonTransition\" Label=\"Nonepsilon transition\" IsTag=\"True\" />"); writer.WriteLine(" <Category Id=\"State\" Label=\"State\" IsTag=\"True\" />"); writer.WriteLine(" </Categories>"); writer.WriteLine(" <Styles>"); writer.WriteLine(" <Style TargetType=\"Node\" GroupLabel=\"InitialState\" ValueLabel=\"True\">"); writer.WriteLine(" <Condition Expression=\"HasCategory('InitialState')\" />"); writer.WriteLine(" <Setter Property=\"Background\" Value=\"lightblue\" />"); writer.WriteLine(" <Setter Property=\"MinWidth\" Value=\"0\" />"); writer.WriteLine(" </Style>"); writer.WriteLine(" <Style TargetType=\"Node\" GroupLabel=\"FinalState\" ValueLabel=\"True\">"); writer.WriteLine(" <Condition Expression=\"HasCategory('FinalState')\" />"); writer.WriteLine(" <Setter Property=\"Background\" Value=\"lightgreen\" />"); writer.WriteLine(" <Setter Property=\"StrokeThickness\" Value=\"4\" />"); writer.WriteLine(" </Style>"); writer.WriteLine(" <Style TargetType=\"Node\" GroupLabel=\"State\" ValueLabel=\"True\">"); writer.WriteLine(" <Condition Expression=\"HasCategory('State')\" />"); writer.WriteLine(" <Setter Property=\"Stroke\" Value=\"black\" />"); writer.WriteLine(" <Setter Property=\"Background\" Value=\"white\" />"); writer.WriteLine(" <Setter Property=\"MinWidth\" Value=\"0\" />"); writer.WriteLine(" <Setter Property=\"FontSize\" Value=\"12\" />"); writer.WriteLine(" <Setter Property=\"FontFamily\" Value=\"Arial\" />"); writer.WriteLine(" </Style>"); writer.WriteLine(" <Style TargetType=\"Link\" GroupLabel=\"NonEpsilonTransition\" ValueLabel=\"True\">"); writer.WriteLine(" <Condition Expression=\"HasCategory('NonEpsilonTransition')\" />"); writer.WriteLine(" <Setter Property=\"FontSize\" Value=\"18\" />"); writer.WriteLine(" <Setter Property=\"FontFamily\" Value=\"Arial\" />"); writer.WriteLine(" </Style>"); writer.WriteLine(" <Style TargetType=\"Link\" GroupLabel=\"StartTransition\" ValueLabel=\"True\">"); writer.WriteLine(" <Condition Expression=\"HasCategory('StartTransition')\" />"); writer.WriteLine(" </Style>"); writer.WriteLine(" <Style TargetType=\"Link\" GroupLabel=\"EpsilonTransition\" ValueLabel=\"True\">"); writer.WriteLine(" <Condition Expression=\"HasCategory('EpsilonTransition')\" />"); writer.WriteLine(" <Setter Property=\"StrokeDashArray\" Value=\"8 8\" />"); writer.WriteLine(" </Style>"); writer.WriteLine(" <Style TargetType=\"Link\" GroupLabel=\"FinalLabel\" ValueLabel=\"False\">"); writer.WriteLine(" <Condition Expression=\"HasCategory('FinalLabel')\" />"); writer.WriteLine(" <Setter Property=\"StrokeDashArray\" Value=\"8 8\" />"); writer.WriteLine(" </Style>"); writer.WriteLine(" <Style TargetType=\"Node\" GroupLabel=\"StateInfo\" ValueLabel=\"True\">"); writer.WriteLine(" <Setter Property=\"Stroke\" Value=\"white\" />"); writer.WriteLine(" <Setter Property=\"FontSize\" Value=\"18\" />"); writer.WriteLine(" <Setter Property=\"FontFamily\" Value=\"Arial\" />"); writer.WriteLine(" </Style>"); writer.WriteLine(" <Style TargetType=\"Node\" GroupLabel=\"DFAInfo\" ValueLabel=\"True\">"); writer.WriteLine(" <Setter Property=\"Stroke\" Value=\"white\" />"); writer.WriteLine(" <Setter Property=\"FontSize\" Value=\"18\" />"); writer.WriteLine(" <Setter Property=\"FontFamily\" Value=\"Arial\" />"); writer.WriteLine(" </Style>"); writer.WriteLine(" </Styles>"); writer.WriteLine("</DirectedGraph>"); }