├── tests └── DotParser.Tests │ ├── paket.references │ ├── App.config │ ├── DotParser.Tests.fsproj │ └── Tests.fs ├── docs ├── files │ └── img │ │ ├── logo.png │ │ └── logo-template.pdn ├── content │ ├── tutorial.fsx │ └── index.fsx └── tools │ ├── templates │ └── template.cshtml │ └── generate.fsx ├── README.md ├── .travis.yml ├── RELEASE_NOTES.md ├── .gitattributes ├── src └── DotParser │ ├── Library.fs │ ├── Lexer.fsl │ ├── Grammar.yrd │ ├── GraphData.fs │ └── DotParser.fsproj ├── LICENSE.txt ├── DotParser.sln └── .gitignore /tests/DotParser.Tests/paket.references: -------------------------------------------------------------------------------- 1 | File:FsUnit.fs 2 | FSharp.Core 3 | NUnit -------------------------------------------------------------------------------- /docs/files/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auduchinok/DotParser/HEAD/docs/files/img/logo.png -------------------------------------------------------------------------------- /docs/files/img/logo-template.pdn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auduchinok/DotParser/HEAD/docs/files/img/logo-template.pdn -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DotParser 2 | 3 | Parser for Graphviz dot language. 4 | Parsed graph stores info about vertices, edges and attributes. 5 | Drawing options are ignored. -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: csharp 2 | 3 | sudo: false # use the new container-based Travis infrastructure 4 | 5 | before_install: 6 | - chmod +x build.sh 7 | 8 | script: 9 | - ./build.sh All 10 | -------------------------------------------------------------------------------- /docs/content/tutorial.fsx: -------------------------------------------------------------------------------- 1 | (*** hide ***) 2 | // This block of code is omitted in the generated HTML documentation. Use 3 | // it to define helpers that you do not want to show in the documentation. 4 | #I "../../bin" 5 | 6 | (** 7 | Introducing your project 8 | ======================== 9 | 10 | Say more 11 | 12 | *) 13 | #r "DotParser.dll" 14 | open DotParser 15 | 16 | Library.hello 0 17 | (** 18 | Some more info 19 | *) 20 | -------------------------------------------------------------------------------- /RELEASE_NOTES.md: -------------------------------------------------------------------------------- 1 | ### 1.0.6 - August 17 2016 2 | * Merge libraries into single dll 3 | 4 | ### 1.0.5 - July 12 2016 5 | * Fix references 6 | 7 | ### 1.0.4 - July 12 2016 8 | * Use libs targeting F# 4.0 only 9 | 10 | ### 1.0.3 - July 12 2016 11 | * Update required libs 12 | 13 | ### 1.0.2 - July 5 2016 14 | * Fix NuGet package creation 15 | 16 | ### 1.0.1 - July 5 2016 17 | * Fix NuGet package creation 18 | 19 | ### 1.0.0 - June 1 2016 20 | * Move from YaccConstructor/QuickGraph 21 | -------------------------------------------------------------------------------- /tests/DotParser.Tests/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | True 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp text=auto eol=lf 6 | *.vb diff=csharp text=auto eol=lf 7 | *.fs diff=csharp text=auto eol=lf 8 | *.fsi diff=csharp text=auto eol=lf 9 | *.fsx diff=csharp text=auto eol=lf 10 | *.sln text eol=crlf merge=union 11 | *.csproj merge=union 12 | *.vbproj merge=union 13 | *.fsproj merge=union 14 | *.dbproj merge=union 15 | 16 | # Standard to msysgit 17 | *.doc diff=astextplain 18 | *.DOC diff=astextplain 19 | *.docx diff=astextplain 20 | *.DOCX diff=astextplain 21 | *.dot diff=astextplain 22 | *.DOT diff=astextplain 23 | *.pdf diff=astextplain 24 | *.PDF diff=astextplain 25 | *.rtf diff=astextplain 26 | *.RTF diff=astextplain 27 | -------------------------------------------------------------------------------- /tests/DotParser.Tests/DotParser.Tests.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/DotParser/Library.fs: -------------------------------------------------------------------------------- 1 | // Eugene Auduchinok, 2016 2 | 3 | [] 4 | module DotParser.DotParser 5 | 6 | open Yard.Generators.RNGLR.Parser 7 | open Yard.Generators.Common.AST 8 | open DotParser.Parser 9 | open Microsoft.FSharp.Text 10 | 11 | let parse str = 12 | let translateArgs = 13 | { tokenToRange = fun _ -> Unchecked.defaultof<_>, Unchecked.defaultof<_> 14 | zeroPosition = Unchecked.defaultof<_> 15 | clearAST = false 16 | filterEpsilons = false } 17 | 18 | let lexbuf = Lexing.LexBuffer<_>.FromString(str) 19 | let tokens = seq { while not lexbuf.IsPastEndOfStream do yield tokenize lexbuf } 20 | 21 | let parsedGraphDataList: GraphData list = 22 | match buildAst tokens with 23 | | Error(pos, token, msg, _, _) -> failwithf "DotParser: error on position %d, token %A: %s" pos token msg 24 | | Success(ast, _, errors) -> translate translateArgs ast errors 25 | 26 | match parsedGraphDataList with 27 | | data :: _ -> data 28 | | [] -> failwith "DotParser: could not translate ast" 29 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /src/DotParser/Lexer.fsl: -------------------------------------------------------------------------------- 1 | { 2 | 3 | [] 4 | module internal DotParser.Lexer 5 | 6 | open DotParser.Parser 7 | open Microsoft.FSharp.Text.Lexing 8 | 9 | let lexeme = LexBuffer<_>.LexemeString 10 | 11 | let keywords = 12 | [ 13 | "graph", GRAPH; 14 | "digraph", DIGRAPH; 15 | "strict", STRICT; 16 | "subgraph", SUBGRAPH; 17 | "node", NODE; 18 | "edge", EDGE; 19 | ] |> Map.ofList 20 | 21 | } 22 | 23 | let digit = ['0'-'9'] 24 | let whitespace = [' ' '\t' '\r' '\n'] 25 | let id = ['a'-'z' 'A'-'Z' '_'](['a'-'z' 'A'-'Z' '_' '0'-'9'])*|['-']?('.'['0'-'9']+|['0'-'9']+('.'['0'-'9']+)?) 26 | 27 | rule tokenize = parse 28 | | whitespace { tokenize lexbuf } 29 | | '"' { ID ( (string lexbuf.StartPos "" lexbuf) ) } 30 | | '{' { LBRACE <| lexeme lexbuf } 31 | | '}' { RBRACE <| lexeme lexbuf } 32 | | '[' { LBRACK <| lexeme lexbuf } 33 | | ']' { RBRACK <| lexeme lexbuf } 34 | | '=' { ASSIGN <| lexeme lexbuf } 35 | | ';' { SEMI <| lexeme lexbuf } 36 | | ':' { COLON <| lexeme lexbuf } 37 | | ',' { COMMA <| lexeme lexbuf } 38 | | "--" { EDGE <| lexeme lexbuf } 39 | | "->" { DIEDGE <| lexeme lexbuf } 40 | | id { match keywords.TryFind((lexeme lexbuf).ToLower()) with 41 | | Some(keyword) -> keyword <| (lexeme lexbuf).ToLower() 42 | | None -> ID <| lexeme lexbuf } 43 | | eof { RNGLR_EOF <| lexeme lexbuf } 44 | | _ { failwithf "unexpected input: %s" <| lexeme lexbuf } 45 | 46 | and string pos s = parse 47 | | "\\\"" { string pos (s + "\"") lexbuf } 48 | | "\\" { string pos s lexbuf } 49 | | "\r" { string pos s lexbuf } 50 | | "\"" { s } 51 | | "\n" { lexbuf.EndPos <- lexbuf.EndPos.NextLine; 52 | string pos s lexbuf } 53 | | eof { failwithf "end of file in string started at or near %A" pos } 54 | | _ { string pos (s + (lexeme lexbuf)) lexbuf } -------------------------------------------------------------------------------- /src/DotParser/Grammar.yrd: -------------------------------------------------------------------------------- 1 | // Eugene Auduchinok, 2016 2 | 3 | { 4 | open DotParser 5 | open Option 6 | 7 | let map = Map.ofList 8 | let wrongEdge = failwithf "DotParser: unexpected token: %s" 9 | 10 | let inline getOrElse v = 11 | function 12 | | Some x -> x 13 | | None -> v 14 | } 15 | 16 | tokens { 17 | _ of string 18 | } 19 | 20 | options { 21 | translate = true 22 | module = "Parser" 23 | infEpsPath = epsilons 24 | pos = uint64 25 | } 26 | 27 | module Grammar 28 | 29 | [] 30 | graph: g=graph_type [ID] LBRACE g1=stmt_list<> RBRACE { g1 } 31 | 32 | graph_type: s=[STRICT] d=(GRAPH { false } | DIGRAPH { true }) { emptyGraph d (isSome s) } 33 | 34 | stmt_list<<(g: GraphData)>>: 35 | g1=[s=stmt<> [SEMI] s1=[stmt_list<>] { getOrElse s s1 }] { getOrElse g g1 } 36 | 37 | stmt<<(g: GraphData)>>: 38 | g1=node_stmt<> { g1 } 39 | | g1=edge_stmt<> { g1 } 40 | | g1=attr_stmt<> { g1 } 41 | | g1=subgraph<> { fst g1 } 42 | | ID ASSIGN ID { g } 43 | 44 | 45 | attr_stmt<<(g: GraphData)>>: 46 | k=(GRAPH { "graph" } | NODE { "node" } | EDGE { "edge" }) a=attr_list { addAttributes g k a } 47 | 48 | attr_list: LBRACK a=[a_list] RBRACK { getOrElse Map.empty a } 49 | 50 | a_list: 51 | k=ID ASSIGN v=ID [SEMI { } | COMMA { }] l=[a_list] { Map.add k v (getOrElse Map.empty l) } 52 | 53 | node_stmt<<(g: GraphData)>>: n=ID [port { }] a=[attr_list] { addNode g n (getOrElse Map.empty a) |> fst } 54 | 55 | edge_stmt<<(g: GraphData)>>: 56 | n=nodes<> g1=edge_rhs<<(fst n, [snd n])>> a=[attr_list] { addEdgesForList (fst g1) (snd g1) (getOrElse Map.empty a) } 57 | 58 | edge_rhs<<(d: GraphData * string list list)>>: 59 | edgeop<<(fst d)>> n=nodes<<(fst d)>> r=[edge_rhs<<(fst n, (snd n) :: (snd d))>>] { if isSome r then r.Value else (fst n), ((snd n) :: (snd d)) } 60 | 61 | nodes<<(g: GraphData)>>: n=node_id<> { n } | n=subgraph<> { n } 62 | 63 | edgeop<<(g: GraphData)>>: 64 | EDGE { if g.IsDirected then wrongEdge "--" else () } 65 | | DIEDGE { if not <| g.IsDirected then wrongEdge "->" else () } 66 | 67 | node_id<<(g: GraphData)>>: name=ID [port { }] { addNode g name Map.empty } 68 | 69 | subgraph<<(g: GraphData)>>: 70 | [SUBGRAPH [ID] { }] LBRACE s=stmt_list<<(copyAttrs g)>> RBRACE { addSubgraph g s } 71 | 72 | port: COLON ID [COLON ID { }] { } 73 | -------------------------------------------------------------------------------- /docs/content/index.fsx: -------------------------------------------------------------------------------- 1 | (*** hide ***) 2 | // This block of code is omitted in the generated HTML documentation. Use 3 | // it to define helpers that you do not want to show in the documentation. 4 | #I "../../bin" 5 | 6 | (** 7 | DotParser 8 | ====================== 9 | 10 | Documentation 11 | 12 |
13 |
14 |
15 |
16 | The DotParser library can be installed from NuGet: 17 |
PM> Install-Package DotParser
18 |
19 |
20 |
21 |
22 | 23 | Example 24 | ------- 25 | 26 | This example demonstrates using a function defined in this sample library. 27 | 28 | *) 29 | #r "DotParser.dll" 30 | open DotParser 31 | 32 | printfn "hello = %i" <| Library.hello 0 33 | 34 | (** 35 | Some more info 36 | 37 | Samples & documentation 38 | ----------------------- 39 | 40 | The library comes with comprehensible documentation. 41 | It can include tutorials automatically generated from `*.fsx` files in [the content folder][content]. 42 | The API reference is automatically generated from Markdown comments in the library implementation. 43 | 44 | * [Tutorial](tutorial.html) contains a further explanation of this sample library. 45 | 46 | * [API Reference](reference/index.html) contains automatically generated documentation for all types, modules 47 | and functions in the library. This includes additional brief samples on using most of the 48 | functions. 49 | 50 | Contributing and copyright 51 | -------------------------- 52 | 53 | The project is hosted on [GitHub][gh] where you can [report issues][issues], fork 54 | the project and submit pull requests. If you're adding a new public API, please also 55 | consider adding [samples][content] that can be turned into a documentation. You might 56 | also want to read the [library design notes][readme] to understand how it works. 57 | 58 | The library is available under Public Domain license, which allows modification and 59 | redistribution for both commercial and non-commercial purposes. For more information see the 60 | [License file][license] in the GitHub repository. 61 | 62 | [content]: https://github.com/fsprojects/DotParser/tree/master/docs/content 63 | [gh]: https://github.com/fsprojects/DotParser 64 | [issues]: https://github.com/fsprojects/DotParser/issues 65 | [readme]: https://github.com/fsprojects/DotParser/blob/master/README.md 66 | [license]: https://github.com/fsprojects/DotParser/blob/master/LICENSE.txt 67 | *) 68 | -------------------------------------------------------------------------------- /docs/tools/templates/template.cshtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | @Title 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | 23 |
24 |
25 | 29 |

@Properties["project-name"]

30 |
31 |
32 |
33 |
34 | @RenderBody() 35 |
36 |
37 | F# Project 38 | 53 |
54 |
55 |
56 | Fork me on GitHub 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/DotParser/GraphData.fs: -------------------------------------------------------------------------------- 1 | // Eugene Auduchinok, 2016 2 | 3 | namespace DotParser 4 | 5 | type Attributes = Map 6 | 7 | type GraphData = 8 | { IsDirected: bool 9 | IsStrict: bool 10 | Subgraphs: GraphData list 11 | Nodes: Map 12 | Edges: Map 13 | GraphAttributes: Attributes 14 | NodeAttributes: Attributes 15 | EdgeAttributes: Attributes } 16 | 17 | 18 | [] 19 | module GraphData = 20 | [] 21 | let emptyGraph d s = 22 | { IsDirected = d 23 | IsStrict = s 24 | Subgraphs = [] 25 | Nodes = Map.empty 26 | Edges = Map.empty 27 | GraphAttributes = Map.empty 28 | NodeAttributes = Map.empty 29 | EdgeAttributes = Map.empty } 30 | 31 | let copyAttrs (g: GraphData) = 32 | { g with Nodes = Map.empty; Edges = Map.empty ; Subgraphs = []} 33 | 34 | 35 | let merge = Map.fold (fun acc key value -> Map.add key value acc) 36 | 37 | let addAttributes (g: GraphData) (key: string) (a: Attributes) = 38 | match key with 39 | | "graph" -> { g with GraphAttributes = merge g.GraphAttributes a } 40 | | "node" -> { g with NodeAttributes = merge g.NodeAttributes a } 41 | | "edge" -> { g with EdgeAttributes = merge g.EdgeAttributes a } 42 | | _ -> failwithf "DotParser: parser error: wrong attribute key: %s" key 43 | 44 | 45 | let addNode (g: GraphData) (n: string) (a: Attributes) = 46 | let a = 47 | match g.Nodes.TryFind n with 48 | | Some attributes -> merge attributes a 49 | | None -> a 50 | { g with Nodes = Map.add n (merge g.NodeAttributes a) g.Nodes }, [n] 51 | 52 | 53 | let addEdge (g: GraphData) (n1: string) (n2: string) (a: Attributes) = 54 | let edge = if (not <| g.IsDirected) && n2 < n1 then n2, n1 else n1, n2 55 | let newEdges = 56 | match g.Edges.TryFind edge, g.IsStrict with 57 | | Some oldEdges, true -> [merge (merge (List.head oldEdges) g.EdgeAttributes) a] 58 | | Some oldEdges, false -> (merge g.EdgeAttributes a):: oldEdges 59 | | _ -> [merge g.EdgeAttributes a] 60 | 61 | { g with Edges = Map.add edge newEdges g.Edges } 62 | 63 | 64 | let addEdges (g: GraphData) (ns1: string list) (ns2: string list) (a: Attributes) = 65 | (List.fold (fun acc n1 -> List.fold (fun acc n2 -> addEdge acc n2 n1 a) acc ns2) g ns1), ns2 66 | 67 | let addEdgesForList (g: GraphData) (ns: string list list) (a: Attributes) = 68 | List.fold (fun (g, n1) n2 -> addEdges g n1 n2 a) (g, ns.Head) ns.Tail |> fst 69 | 70 | let addSubgraph (g: GraphData) (s: GraphData) = 71 | let addSubgraph g = {g with Subgraphs = s :: g.Subgraphs} 72 | let addNodes g = Map.fold (fun acc n attr -> fst <| addNode acc n attr) g s.Nodes 73 | let addParallelEdges n1 n2 = List.fold (fun acc x -> addEdge acc n1 n2 x) 74 | let addEdges g = Map.fold (fun acc (n1, n2) attr -> addParallelEdges n1 n2 acc attr) g s.Edges 75 | g |> addSubgraph |> addNodes |> addEdges, s.Nodes |> Map.toList |> List.map fst 76 | -------------------------------------------------------------------------------- /DotParser.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio 15 3 | VisualStudioVersion = 15.0.27703.2042 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".paket", ".paket", "{63297B98-5CED-492C-A5B7-A5B4F73CF142}" 6 | ProjectSection(SolutionItems) = preProject 7 | paket.dependencies = paket.dependencies 8 | paket.lock = paket.lock 9 | EndProjectSection 10 | EndProject 11 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{A6A6AF7D-D6E3-442D-9B1E-58CC91879BE1}" 12 | EndProject 13 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "DotParser", "src\DotParser\DotParser.fsproj", "{63C37483-04C3-42DA-87AE-5D1225AF713A}" 14 | EndProject 15 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "project", "project", "{BF60BC93-E09B-4E5F-9D85-95A519479D54}" 16 | ProjectSection(SolutionItems) = preProject 17 | build.fsx = build.fsx 18 | README.md = README.md 19 | RELEASE_NOTES.md = RELEASE_NOTES.md 20 | EndProjectSection 21 | EndProject 22 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{83F16175-43B1-4C90-A1EE-8E351C33435D}" 23 | ProjectSection(SolutionItems) = preProject 24 | docs\tools\generate.fsx = docs\tools\generate.fsx 25 | docs\tools\templates\template.cshtml = docs\tools\templates\template.cshtml 26 | EndProjectSection 27 | EndProject 28 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "content", "content", "{8E6D5255-776D-4B61-85F9-73C37AA1FB9A}" 29 | ProjectSection(SolutionItems) = preProject 30 | docs\content\index.fsx = docs\content\index.fsx 31 | docs\content\tutorial.fsx = docs\content\tutorial.fsx 32 | EndProjectSection 33 | EndProject 34 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{ED8079DD-2B06-4030-9F0F-DC548F98E1C4}" 35 | EndProject 36 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "DotParser.Tests", "tests\DotParser.Tests\DotParser.Tests.fsproj", "{460BDE6C-0582-4FBF-8015-84AD98E65760}" 37 | EndProject 38 | Global 39 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 40 | Debug|Any CPU = Debug|Any CPU 41 | Release|Any CPU = Release|Any CPU 42 | EndGlobalSection 43 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 44 | {63C37483-04C3-42DA-87AE-5D1225AF713A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {63C37483-04C3-42DA-87AE-5D1225AF713A}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {63C37483-04C3-42DA-87AE-5D1225AF713A}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {63C37483-04C3-42DA-87AE-5D1225AF713A}.Release|Any CPU.Build.0 = Release|Any CPU 48 | {460BDE6C-0582-4FBF-8015-84AD98E65760}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {460BDE6C-0582-4FBF-8015-84AD98E65760}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {460BDE6C-0582-4FBF-8015-84AD98E65760}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 | {460BDE6C-0582-4FBF-8015-84AD98E65760}.Release|Any CPU.Build.0 = Release|Any CPU 52 | EndGlobalSection 53 | GlobalSection(SolutionProperties) = preSolution 54 | HideSolutionNode = FALSE 55 | EndGlobalSection 56 | GlobalSection(NestedProjects) = preSolution 57 | {83F16175-43B1-4C90-A1EE-8E351C33435D} = {A6A6AF7D-D6E3-442D-9B1E-58CC91879BE1} 58 | {8E6D5255-776D-4B61-85F9-73C37AA1FB9A} = {A6A6AF7D-D6E3-442D-9B1E-58CC91879BE1} 59 | {460BDE6C-0582-4FBF-8015-84AD98E65760} = {ED8079DD-2B06-4030-9F0F-DC548F98E1C4} 60 | EndGlobalSection 61 | GlobalSection(ExtensibilityGlobals) = postSolution 62 | SolutionGuid = {F7589EAC-9F18-42DF-9616-1A7F3062A7BF} 63 | EndGlobalSection 64 | EndGlobal 65 | -------------------------------------------------------------------------------- /src/DotParser/DotParser.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net461;netstandard2.0 4 | true 5 | 6 | 1.0.7 7 | DotParser 8 | Graphviz dot parser 9 | auduchinok 10 | 11 | 12 | 13 | 0.2.8.16 14 | 2.0.15 15 | 16 | $(NuGetPackageRoot)yc.fsyard\$(FsYardVersion)\lib\net40\YC.YaccConstructor.exe 17 | $(NuGetPackageRoot)yc.fsyard\$(FsYardVersion)\lib\net40\AbstractLexer.Generator.exe 18 | $(NuGetPackageRoot)ilrepack\$(ILRepackVersion)\tools\ILRepack.exe 19 | false 20 | false 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | all 38 | 39 | 40 | 41 | all 42 | 43 | 44 | 45 | all 46 | 47 | 48 | 49 | all 50 | 51 | 52 | 53 | all 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | $(OutputPath)\DotParser.dll $(OutputPath)\AST.Common.dll $(OutputPath)\FsLexYacc.Runtime.dll $(OutputPath)\YC.RNGLRCommon.dll $(OutputPath)\YC.RNGLRParser.dll $(OutputPath)\YC.RNGLRAbstractParser.dll $(OutputPath)\AST.Common.dll $(OutputPath)\YC.AbstractAnalysis.Common.dll $(OutputPath)\QuickGraph.dll 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore generated files 2 | src/DotParser/out 3 | src/DotParser/Lexer.fs 4 | src/DotParser/Lexer_Abstract.fs 5 | src/DotParser/Grammar.yrd.fs 6 | res.dot 7 | 8 | ## Ignore Visual Studio temporary files, build results, and 9 | ## files generated by popular Visual Studio add-ons. 10 | 11 | # User-specific files 12 | *.suo 13 | *.user 14 | *.sln.docstates 15 | 16 | # Xamarin Studio / monodevelop user-specific 17 | *.userprefs 18 | *.dll.mdb 19 | *.exe.mdb 20 | 21 | # Build results 22 | 23 | [Dd]ebug/ 24 | [Rr]elease/ 25 | x64/ 26 | build/ 27 | [Bb]in/ 28 | [Oo]bj/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | *_i.c 35 | *_p.c 36 | *.ilk 37 | *.meta 38 | *.obj 39 | *.pch 40 | *.pdb 41 | *.pgc 42 | *.pgd 43 | *.rsp 44 | *.sbr 45 | *.tlb 46 | *.tli 47 | *.tlh 48 | *.tmp 49 | *.tmp_proj 50 | *.log 51 | *.vspscc 52 | *.vssscc 53 | .builds 54 | *.pidb 55 | *.log 56 | *.scc 57 | 58 | # Visual C++ cache files 59 | ipch/ 60 | *.aps 61 | *.ncb 62 | *.opensdf 63 | *.sdf 64 | *.cachefile 65 | 66 | # Visual Studio profiler 67 | *.psess 68 | *.vsp 69 | *.vspx 70 | 71 | # Other Visual Studio data 72 | .vs/ 73 | 74 | # Guidance Automation Toolkit 75 | *.gpState 76 | 77 | # ReSharper is a .NET coding add-in 78 | _ReSharper*/ 79 | *.[Rr]e[Ss]harper 80 | 81 | # TeamCity is a build add-in 82 | _TeamCity* 83 | 84 | # DotCover is a Code Coverage Tool 85 | *.dotCover 86 | 87 | # NCrunch 88 | *.ncrunch* 89 | .*crunch*.local.xml 90 | 91 | # Installshield output folder 92 | [Ee]xpress/ 93 | 94 | # DocProject is a documentation generator add-in 95 | DocProject/buildhelp/ 96 | DocProject/Help/*.HxT 97 | DocProject/Help/*.HxC 98 | DocProject/Help/*.hhc 99 | DocProject/Help/*.hhk 100 | DocProject/Help/*.hhp 101 | DocProject/Help/Html2 102 | DocProject/Help/html 103 | 104 | # Click-Once directory 105 | publish/ 106 | 107 | # Publish Web Output 108 | *.Publish.xml 109 | 110 | # Enable nuget.exe in the .nuget folder (though normally executables are not tracked) 111 | !.nuget/NuGet.exe 112 | 113 | # Windows Azure Build Output 114 | csx 115 | *.build.csdef 116 | 117 | # Windows Store app package directory 118 | AppPackages/ 119 | 120 | # VSCode 121 | .vscode/ 122 | 123 | # Others 124 | sql/ 125 | *.Cache 126 | ClientBin/ 127 | [Ss]tyle[Cc]op.* 128 | ~$* 129 | *~ 130 | *.dbmdl 131 | *.[Pp]ublish.xml 132 | *.pfx 133 | *.publishsettings 134 | 135 | # RIA/Silverlight projects 136 | Generated_Code/ 137 | 138 | # Backup & report files from converting an old project file to a newer 139 | # Visual Studio version. Backup files are not needed, because we have git ;-) 140 | _UpgradeReport_Files/ 141 | Backup*/ 142 | UpgradeLog*.XML 143 | UpgradeLog*.htm 144 | 145 | # SQL Server files 146 | App_Data/*.mdf 147 | App_Data/*.ldf 148 | 149 | 150 | #LightSwitch generated files 151 | GeneratedArtifacts/ 152 | _Pvt_Extensions/ 153 | ModelManifest.xml 154 | 155 | # ========================= 156 | # Windows detritus 157 | # ========================= 158 | 159 | # Windows image file caches 160 | Thumbs.db 161 | ehthumbs.db 162 | 163 | # Folder config file 164 | Desktop.ini 165 | 166 | # Recycle Bin used on file shares 167 | $RECYCLE.BIN/ 168 | 169 | # Mac desktop service store files 170 | .DS_Store 171 | 172 | # =================================================== 173 | # Exclude F# project specific directories and files 174 | # =================================================== 175 | 176 | # NuGet Packages Directory 177 | packages/ 178 | 179 | # Generated documentation folder 180 | docs/output/ 181 | 182 | # Temp folder used for publishing docs 183 | temp/ 184 | 185 | # Test results produced by build 186 | TestResult.xml 187 | 188 | # Nuget outputs 189 | nuget/*.nupkg 190 | release.cmd 191 | release.sh 192 | localpackages/ 193 | paket-files 194 | *.orig 195 | .paket/paket.exe 196 | docs/content/license.md 197 | docs/content/release-notes.md 198 | .fake 199 | docs/tools/FSharp.Formatting.svclog 200 | -------------------------------------------------------------------------------- /docs/tools/generate.fsx: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------- 2 | // Builds the documentation from `.fsx` and `.md` files in the 'docs/content' directory 3 | // (the generated documentation is stored in the 'docs/output' directory) 4 | // -------------------------------------------------------------------------------------- 5 | 6 | // Binaries that have XML documentation (in a corresponding generated XML file) 7 | // Any binary output / copied to bin/projectName/projectName.dll will 8 | // automatically be added as a binary to generate API docs for. 9 | // for binaries output to root bin folder please add the filename only to the 10 | // referenceBinaries list below in order to generate documentation for the binaries. 11 | // (This is the original behaviour of ProjectScaffold prior to multi project support) 12 | let referenceBinaries = [] 13 | // Web site location for the generated documentation 14 | let website = "/DotParser" 15 | 16 | let githubLink = "http://github.com/auduchinok/DotParser" 17 | 18 | // Specify more information about your project 19 | let info = 20 | [ "project-name", "DotParser" 21 | "project-author", "Eugene Auduchinok" 22 | "project-summary", "Project has no summmary; update build.fsx" 23 | "project-github", githubLink 24 | "project-nuget", "http://nuget.org/packages/DotParser" ] 25 | 26 | // -------------------------------------------------------------------------------------- 27 | // For typical project, no changes are needed below 28 | // -------------------------------------------------------------------------------------- 29 | 30 | #load "../../packages/build/FSharp.Formatting/FSharp.Formatting.fsx" 31 | #I "../../packages/build/FAKE/tools/" 32 | #r "FakeLib.dll" 33 | open Fake 34 | open System.IO 35 | open Fake.FileHelper 36 | open FSharp.Literate 37 | open FSharp.MetadataFormat 38 | 39 | // When called from 'build.fsx', use the public project URL as 40 | // otherwise, use the current 'output' directory. 41 | #if RELEASE 42 | let root = website 43 | #else 44 | let root = "file://" + (__SOURCE_DIRECTORY__ @@ "../output") 45 | #endif 46 | 47 | // Paths with template/source/output locations 48 | let bin = __SOURCE_DIRECTORY__ @@ "../../bin" 49 | let content = __SOURCE_DIRECTORY__ @@ "../content" 50 | let output = __SOURCE_DIRECTORY__ @@ "../output" 51 | let files = __SOURCE_DIRECTORY__ @@ "../files" 52 | let templates = __SOURCE_DIRECTORY__ @@ "templates" 53 | let formatting = __SOURCE_DIRECTORY__ @@ "../../packages/build/FSharp.Formatting/" 54 | let docTemplate = "docpage.cshtml" 55 | 56 | // Where to look for *.csproj templates (in this order) 57 | let layoutRootsAll = new System.Collections.Generic.Dictionary() 58 | layoutRootsAll.Add("en",[ templates; formatting @@ "templates" 59 | formatting @@ "templates/reference" ]) 60 | subDirectories (directoryInfo templates) 61 | |> Seq.iter (fun d -> 62 | let name = d.Name 63 | if name.Length = 2 || name.Length = 3 then 64 | layoutRootsAll.Add( 65 | name, [templates @@ name 66 | formatting @@ "templates" 67 | formatting @@ "templates/reference" ])) 68 | 69 | // Copy static files and CSS + JS from F# Formatting 70 | let copyFiles () = 71 | CopyRecursive files output true |> Log "Copying file: " 72 | ensureDirectory (output @@ "content") 73 | CopyRecursive (formatting @@ "styles") (output @@ "content") true 74 | |> Log "Copying styles and scripts: " 75 | 76 | let binaries = 77 | let manuallyAdded = 78 | referenceBinaries 79 | |> List.map (fun b -> bin @@ b) 80 | 81 | let conventionBased = 82 | directoryInfo bin 83 | |> subDirectories 84 | |> Array.map (fun d -> d.FullName @@ (sprintf "%s.dll" d.Name)) 85 | |> List.ofArray 86 | 87 | conventionBased @ manuallyAdded 88 | 89 | let libDirs = 90 | let conventionBasedbinDirs = 91 | directoryInfo bin 92 | |> subDirectories 93 | |> Array.map (fun d -> d.FullName) 94 | |> List.ofArray 95 | 96 | conventionBasedbinDirs @ [bin] 97 | 98 | // Build API reference from XML comments 99 | let buildReference () = 100 | CleanDir (output @@ "reference") 101 | MetadataFormat.Generate 102 | ( binaries, output @@ "reference", layoutRootsAll.["en"], 103 | parameters = ("root", root)::info, 104 | sourceRepo = githubLink @@ "tree/master", 105 | sourceFolder = __SOURCE_DIRECTORY__ @@ ".." @@ "..", 106 | publicOnly = true,libDirs = libDirs ) 107 | 108 | // Build documentation from `fsx` and `md` files in `docs/content` 109 | let buildDocumentation () = 110 | 111 | // First, process files which are placed in the content root directory. 112 | 113 | Literate.ProcessDirectory 114 | ( content, docTemplate, output, replacements = ("root", root)::info, 115 | layoutRoots = layoutRootsAll.["en"], 116 | generateAnchors = true, 117 | processRecursive = false) 118 | 119 | // And then process files which are placed in the sub directories 120 | // (some sub directories might be for specific language). 121 | 122 | let subdirs = Directory.EnumerateDirectories(content, "*", SearchOption.TopDirectoryOnly) 123 | for dir in subdirs do 124 | let dirname = (new DirectoryInfo(dir)).Name 125 | let layoutRoots = 126 | // Check whether this directory name is for specific language 127 | let key = layoutRootsAll.Keys 128 | |> Seq.tryFind (fun i -> i = dirname) 129 | match key with 130 | | Some lang -> layoutRootsAll.[lang] 131 | | None -> layoutRootsAll.["en"] // "en" is the default language 132 | 133 | Literate.ProcessDirectory 134 | ( dir, docTemplate, output @@ dirname, replacements = ("root", root)::info, 135 | layoutRoots = layoutRoots, 136 | generateAnchors = true ) 137 | 138 | // Generate 139 | copyFiles() 140 | #if HELP 141 | buildDocumentation() 142 | #endif 143 | #if REFERENCE 144 | buildReference() 145 | #endif 146 | -------------------------------------------------------------------------------- /tests/DotParser.Tests/Tests.fs: -------------------------------------------------------------------------------- 1 | module Tests 2 | 3 | open DotParser 4 | open FsUnit 5 | open NUnit.Framework 6 | 7 | let isDirected (g: GraphData) = g.IsDirected 8 | let isStrict (g: GraphData) = g.IsStrict 9 | let isEmpty (g: GraphData) = g.Nodes.IsEmpty 10 | let nodesCount (g: GraphData) = g.Nodes.Count 11 | let edgesCount (g: GraphData) = Map.fold (fun acc _ x -> acc + List.length x) 0 g.Edges 12 | let subgraphsCount (g: GraphData) = g.Subgraphs.Length 13 | 14 | 15 | let rec totalSubgraphsCount (g: GraphData) = 16 | g.Subgraphs.Length 17 | + (g.Subgraphs |> List.fold (fun cnt s -> totalSubgraphsCount s + cnt) 0) 18 | 19 | let shouldContainNodes names (g: GraphData) = 20 | List.forall g.Nodes.ContainsKey names |> should be True 21 | 22 | let map = Map.ofList 23 | let set = Set.ofList 24 | 25 | 26 | [] 27 | let ``Empty undirected graph`` () = DotParser.parse "graph { }" |> isDirected |> should be False 28 | 29 | 30 | [] 31 | let ``Empty directed graph`` () = DotParser.parse "digraph { }" |> isDirected |> should be True 32 | 33 | 34 | [] 35 | let ``Named graph`` () = DotParser.parse "graph t { }" |> isEmpty |> should be True 36 | 37 | 38 | [] 39 | let ``Single node`` () = 40 | let graph = DotParser.parse "graph { a }" 41 | 42 | nodesCount graph |> should equal 1 43 | edgesCount graph |> should equal 0 44 | 45 | graph |> shouldContainNodes ["a"] 46 | 47 | 48 | [] 49 | let ``Multiple nodes`` () = 50 | let graph = DotParser.parse "graph { a b; c \n d \"--\" }" 51 | 52 | nodesCount graph |> should equal 5 53 | edgesCount graph |> should equal 0 54 | 55 | graph |> shouldContainNodes ["a"; "b"; "c"; "d"; "--"] 56 | 57 | 58 | [] 59 | let ``Numeral node labels`` () = 60 | let graph = DotParser.parse "graph { 1 2 }" 61 | 62 | nodesCount graph |> should equal 2 63 | edgesCount graph |> should equal 0 64 | 65 | graph |> shouldContainNodes ["1"; "2"] 66 | 67 | 68 | [] 69 | let ``Single edge`` () = 70 | let graph = DotParser.parse "graph { a -- b } " 71 | 72 | nodesCount graph |> should equal 2 73 | edgesCount graph |> should equal 1 74 | 75 | graph |> shouldContainNodes ["a"; "b"] 76 | 77 | 78 | [] 79 | let ``Multiple edges`` () = 80 | let graph = DotParser.parse "graph { a -- b c -- d }" 81 | 82 | nodesCount graph |> should equal 4 83 | edgesCount graph |> should equal 2 84 | 85 | 86 | 87 | [] 88 | let ``Multiple edges in row`` () = 89 | let graph = DotParser.parse "graph { a -- b c -- d -- e }" 90 | 91 | nodesCount graph |> should equal 5 92 | edgesCount graph |> should equal 3 93 | 94 | 95 | [] 96 | let ``Multi-egde`` () = 97 | let graph = DotParser.parse "graph { a -- b a -- b }" 98 | 99 | nodesCount graph |> should equal 2 100 | edgesCount graph |> should equal 2 101 | 102 | 103 | [] 104 | let ``Strict graph`` () = 105 | let graph = DotParser.parse "strict graph { a -- b a -- b }" 106 | 107 | isStrict graph |> should be True 108 | nodesCount graph |> should equal 2 109 | edgesCount graph |> should equal 1 110 | 111 | 112 | [] 113 | let ``Keyword labels`` () = 114 | DotParser.parse "graph { \"graph\" \"digraph\" \"strict\" \"node\" \"edge\" \"subgraph\" }" 115 | |> shouldContainNodes ["graph"; "digraph"; "strict"; "node"; "edge"; "subgraph"; ] 116 | 117 | 118 | [] 119 | let ``Wrong edge in directed`` () = 120 | (fun () -> DotParser.parse "graph { a -> b }" |> ignore) 121 | |> should throw typeof 122 | 123 | 124 | [] 125 | let ``Wrong edge in undirected`` () = 126 | (fun () -> DotParser.parse "digraph { a -- b }" |> ignore) 127 | |> should throw typeof 128 | 129 | 130 | [] 131 | let ``Subgraph statement`` () = 132 | let graph = DotParser.parse "graph { a { b c -- d } }" 133 | 134 | graph |> nodesCount |> should equal 4 135 | graph |> edgesCount |> should equal 1 136 | 137 | graph |> subgraphsCount |> should equal 1 138 | graph |> totalSubgraphsCount |> should equal 1 139 | 140 | graph.Subgraphs.[0] |> shouldContainNodes ["b"; "c"; "d"] 141 | 142 | [] 143 | let ``Subgraph on left of edge`` () = 144 | let graph = DotParser.parse "graph { { a b } -- c }" 145 | 146 | nodesCount graph |> should equal 3 147 | edgesCount graph |> should equal 2 148 | 149 | graph |> subgraphsCount |> should equal 1 150 | graph |> totalSubgraphsCount |> should equal 1 151 | 152 | graph |> shouldContainNodes ["a"; "b"; "c"] 153 | graph.Subgraphs.[0] |> shouldContainNodes ["a"; "b"] 154 | 155 | [] 156 | let ``Subgraph on right of edge`` () = 157 | let graph = DotParser.parse "graph { a -- { b c } }" 158 | 159 | nodesCount graph |> should equal 3 160 | edgesCount graph |> should equal 2 161 | 162 | graph |> subgraphsCount |> should equal 1 163 | graph |> totalSubgraphsCount |> should equal 1 164 | 165 | graph |> shouldContainNodes ["a"; "b"; "c"] 166 | graph.Subgraphs.[0] |> shouldContainNodes ["b"; "c"] 167 | 168 | 169 | [] 170 | let ``Subgraph on both sides of edge`` () = 171 | let graph = DotParser.parse "graph { { a b } -- { c d } }" 172 | 173 | nodesCount graph |> should equal 4 174 | edgesCount graph |> should equal 4 175 | 176 | graph |> subgraphsCount |> should equal 2 177 | graph |> totalSubgraphsCount |> should equal 2 178 | 179 | graph |> shouldContainNodes ["a"; "b"; "c"; "d"] 180 | graph.Subgraphs.[1] |> shouldContainNodes ["a"; "b"] 181 | graph.Subgraphs.[0] |> shouldContainNodes ["c"; "d"] 182 | 183 | 184 | [] 185 | let ``Nested subgraphs`` () = 186 | let graph = DotParser.parse "graph { a -- { b -- { c d } } }" 187 | 188 | nodesCount graph |> should equal 4 189 | edgesCount graph |> should equal 5 190 | 191 | graph |> subgraphsCount |> should equal 1 192 | graph |> totalSubgraphsCount |> should equal 2 193 | 194 | graph |> shouldContainNodes ["a"; "b"; "c"; "d"] 195 | graph.Subgraphs.[0] |> shouldContainNodes ["b"; "c"; "d"] 196 | graph.Subgraphs.[0].Subgraphs.[0] |> shouldContainNodes ["c"; "d"] 197 | 198 | 199 | [] 200 | let ``Empty attributes`` () = 201 | let graph = DotParser.parse "graph { edge [ ] graph [ ] node [] }" 202 | 203 | graph.GraphAttributes.IsEmpty |> should be True 204 | graph.NodeAttributes.IsEmpty |> should be True 205 | graph.EdgeAttributes.IsEmpty |> should be True 206 | 207 | 208 | [] 209 | let ``Node attibute`` () = 210 | let graph = DotParser.parse "graph { a node [ color = red ] b }" 211 | graph.Nodes.["a"] |> should equal Map.empty 212 | graph.Nodes.["b"] |> should equal (map ["color", "red"]) 213 | 214 | 215 | [] 216 | let ``Edge attibutes`` () = 217 | let graph = 218 | DotParser.parse "graph { a -- b b -- c edge [ color = red, foo=bar; \"a\" = b ] b--c }" 219 | 220 | graph.Edges.["a", "b"] |> should equal [Map.empty] 221 | graph.Edges.["b", "c"] 222 | |> set |> should equal (set [Map.empty; map ["color", "red"; "foo", "bar"; "a", "b"]]) 223 | 224 | 225 | [] 226 | let ``Strict graph attributes`` () = 227 | let graph = DotParser.parse "strict graph { a -- b a -- b edge [color=red] b -- a }" 228 | 229 | isStrict graph |> should be True 230 | nodesCount graph |> should equal 2 231 | edgesCount graph |> should equal 1 232 | 233 | graph.Edges.["a", "b"] |> should equal [map ["color", "red"]] 234 | 235 | 236 | [] 237 | let ``Edge statement attributes`` () = 238 | let graph = DotParser.parse "graph { a -- b [color=red] }" 239 | 240 | graph.Edges.["a", "b"] |> should equal [map ["color", "red"]] 241 | 242 | [] 243 | let ``Edge statement attributes with escape quotes`` () = 244 | let graph = DotParser.parse """graph { a -- b [color="\"red\""] }""" 245 | 246 | graph.Edges.["a", "b"] |> should equal [map ["color", "\"red\""]] 247 | 248 | [] 249 | let ``Node statement attributes`` () = 250 | let graph = DotParser.parse "graph { a [color=red] b c [color=blue] }" 251 | 252 | nodesCount graph |> should equal 3 253 | 254 | graph.Nodes.["a"] |> should equal (map ["color", "red"]) 255 | graph.Nodes.["b"] |> should equal Map.empty 256 | graph.Nodes.["c"] |> should equal (map ["color", "blue"]) 257 | 258 | 259 | [] 260 | let ``Mixed statement and default attributes with subgraphs`` () = 261 | let graph = 262 | "graph { a -- b edge [color=green] a -- c a -- d [color=red]" + 263 | "a -- { e -- f [color=pink] g -- h }" + 264 | "a -- { edge [color=red] z -- x [color=pink] y -- v } [color=blue] }" 265 | |> DotParser.parse 266 | 267 | let edges = 268 | [("a", "b"), [Map.empty]; 269 | ("a", "c"), [map ["color", "green"]]; ("a", "d"), [map ["color", "red"]]; 270 | ("a", "e"), [map ["color", "green"]]; ("a", "f"), [map ["color", "green"]]; 271 | ("a", "g"), [map ["color", "green"]]; ("a", "h"), [map ["color", "green"]]; 272 | ("a", "z"), [map ["color", "blue"]]; ("a", "v"), [map ["color", "blue"]]; 273 | ("a", "y"), [map ["color", "blue"]]; ("a", "x"), [map ["color", "blue"]]; 274 | ("e", "f"), [map ["color", "pink"]]; ("g", "h"), [map ["color", "green"]]; 275 | ("x", "z"), [map ["color", "pink"]]; ("v", "y"), [map ["color", "red"]]] 276 | 277 | graph.Edges |> should equal (edges |> map) 278 | 279 | 280 | [] 281 | let ``Multiple directed edges`` () = 282 | let graph = DotParser.parse "digraph { a -> b f -> e }" 283 | 284 | graph.Edges.ContainsKey("a", "b") |> should be True 285 | graph.Edges.ContainsKey("f", "e") |> should be True 286 | 287 | 288 | let testNodesInGraph s ns = 289 | DotParser.parse s |> should equal { (emptyGraph false false) with Nodes = map ns} 290 | 291 | 292 | [] 293 | let ``Parse and ignore assign statement`` () = 294 | testNodesInGraph "graph { rank = same; A; B }" ["A", Map.empty; "B", Map.empty] 295 | 296 | 297 | [] 298 | let ``Parse and ignore port, compass pointers`` () = 299 | testNodesInGraph "graph { a : f : n b : g }" ["a", Map.empty; "b", Map.empty] 300 | 301 | [] 302 | let ``Node attributes are preserved after edge attributes`` () = 303 | let graph = 304 | "graph { a[color=red]; b[color=yellow]; a -- b[color=blue] }" 305 | |> DotParser.parse 306 | 307 | graph.Nodes.["a"] |> should equal (map ["color", "red"]) 308 | graph.Nodes.["b"] |> should equal (map ["color", "yellow"]) 309 | --------------------------------------------------------------------------------