From 93b2c760f3f781ed32554e6e790323905d6557bd Mon Sep 17 00:00:00 2001 From: glaxxie <86179463+glaxxie@users.noreply.github.com> Date: Fri, 9 Feb 2024 14:05:39 -0600 Subject: [PATCH] [New Exercise]: Dot DSL (#351) * [New Exercise]: Dot DSL * Update DotDsl.ps1 -Remove the extra constructor in template --- config.json | 8 ++ .../practice/dot-dsl/.docs/instructions.md | 30 +++++ .../practice/dot-dsl/.meta/DotDsl.example.ps1 | 97 +++++++++++++++ exercises/practice/dot-dsl/.meta/config.json | 19 +++ exercises/practice/dot-dsl/DotDsl.ps1 | 56 +++++++++ exercises/practice/dot-dsl/DotDsl.tests.ps1 | 111 ++++++++++++++++++ 6 files changed, 321 insertions(+) create mode 100644 exercises/practice/dot-dsl/.docs/instructions.md create mode 100644 exercises/practice/dot-dsl/.meta/DotDsl.example.ps1 create mode 100644 exercises/practice/dot-dsl/.meta/config.json create mode 100644 exercises/practice/dot-dsl/DotDsl.ps1 create mode 100644 exercises/practice/dot-dsl/DotDsl.tests.ps1 diff --git a/config.json b/config.json index 83a39c2..43f7e73 100644 --- a/config.json +++ b/config.json @@ -960,6 +960,14 @@ "prerequisites": [], "difficulty": 5 }, + { + "slug": "dot-dsl", + "name": "DOT DSL", + "uuid": "79c56891-bb51-4de9-acb1-13edfa14573e", + "practices": [], + "prerequisites": [], + "difficulty": 5 + }, { "slug": "connect", "name": "Connect", diff --git a/exercises/practice/dot-dsl/.docs/instructions.md b/exercises/practice/dot-dsl/.docs/instructions.md new file mode 100644 index 0000000..b3a6399 --- /dev/null +++ b/exercises/practice/dot-dsl/.docs/instructions.md @@ -0,0 +1,30 @@ +# Instructions + +A [Domain Specific Language (DSL)][dsl] is a small language optimized for a specific domain. +Since a DSL is targeted, it can greatly impact productivity/understanding by allowing the writer to declare _what_ they want rather than _how_. + +One problem area where they are applied are complex customizations/configurations. + +For example the [DOT language][dot-language] allows you to write a textual description of a graph which is then transformed into a picture by one of the [Graphviz][graphviz] tools (such as `dot`). +A simple graph looks like this: + + graph { + graph [bgcolor="yellow"] + a [color="red"] + b [color="blue"] + a -- b [color="green"] + } + +Putting this in a file `example.dot` and running `dot example.dot -T png -o example.png` creates an image `example.png` with red and blue circle connected by a green line on a yellow background. + +Write a Domain Specific Language similar to the Graphviz dot language. + +Our DSL is similar to the Graphviz dot language in that our DSL will be used to create graph data structures. +However, unlike the DOT Language, our DSL will be an internal DSL for use only in our language. + +More information about the difference between internal and external DSLs can be found [here][fowler-dsl]. + +[dsl]: https://en.wikipedia.org/wiki/Domain-specific_language +[dot-language]: https://en.wikipedia.org/wiki/DOT_(graph_description_language) +[graphviz]: https://graphviz.org/ +[fowler-dsl]: https://martinfowler.com/bliki/DomainSpecificLanguage.html diff --git a/exercises/practice/dot-dsl/.meta/DotDsl.example.ps1 b/exercises/practice/dot-dsl/.meta/DotDsl.example.ps1 new file mode 100644 index 0000000..afa25e2 --- /dev/null +++ b/exercises/practice/dot-dsl/.meta/DotDsl.example.ps1 @@ -0,0 +1,97 @@ +<# +.SYNOPSIS + Write a Domain Specific Language similar to the Graphviz dot language. + +.DESCRIPTION + Implement the classes to stimulate a DSL similar to the Graphviz dot language. + + Node class : represent the nodes inside the graph. + Edge class : represent the relationship between two nodes. + Attr class : represent the attributes of other objects (node, edge or graph). + Graph class: represent the graph, contains info about nodes, edges and attributes. + + Node, Edge and Attr should have the 'Equals' method implemented for the purpose of comparison in the test suite. +#> + +Class Node { + [string] $Name + [Attr] $Attrs + + Node([string] $name){ + $this.Name = $name + $this.Attrs = [Attr]::new() + } + + Node([string] $name, [hashtable] $attrs){ + $this.Name = $name + $this.Attrs = [Attr]::new($attrs) + } + + [bool] Equals([object] $other) { + return $this.Name -eq $other.Name -and $this.Attrs -eq $other.Attrs + } +} + +Class Edge { + [string] $Source + [string] $Target + [Attr] $Attrs + + Edge([string] $from, [string] $to){ + $this.Source = $from + $this.Target = $to + $this.Attrs = [Attr]::new() + } + + Edge([string] $from, [string] $to, [hashtable] $attr){ + $this.Source = $from + $this.Target = $to + $this.Attrs = [Attr]::new($attr) + } + + [bool] Equals([object] $other) { + return $this.Source -eq $other.Source + -and $this.Target -eq $other.Target + -and $this.Attrs -eq $other.Attrs + } +} + +Class Attr { + [hashtable] hidden $Data + + Attr() { + $this.Data = @{} + } + + Attr([hashtable] $attr){ + $this.Data = @{} + $this.Data += $attr + } + + [bool] Equals( $other) { + $equalsKey = Compare-Object $this.Data.Keys $other.Data.Keys + $equalsVal = Compare-Object $this.Data.Values $other.Data.Values + return -not ($equalsKey -or $equalsVal) + } +} + +Class Graph { + [Node[]] $Nodes + [Edge[]] $Edges + [Attr[]] $Attrs + + Graph(){ + $this.Nodes = @() + $this.Edges = @() + $this.Attrs = @() + } + + Graph([object[]] $Data){ + switch ($Data) { + {$_ -is [Node]} { $this.Nodes += $_ } + {$_ -is [Edge]} { $this.Edges += $_ } + {$_ -is [Attr]} { $this.Attrs += $_ } + Default { Throw "Graph can only contain node, egde or attribute"} + } + } +} \ No newline at end of file diff --git a/exercises/practice/dot-dsl/.meta/config.json b/exercises/practice/dot-dsl/.meta/config.json new file mode 100644 index 0000000..6fc0fb6 --- /dev/null +++ b/exercises/practice/dot-dsl/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "glaxxie" + ], + "files": { + "solution": [ + "DotDsl.ps1" + ], + "test": [ + "DotDsl.tests.ps1" + ], + "example": [ + ".meta/DotDsl.example.ps1" + ] + }, + "blurb": "Write a Domain Specific Language similar to the Graphviz dot language.", + "source": "Wikipedia", + "source_url": "https://en.wikipedia.org/wiki/DOT_(graph_description_language)" +} diff --git a/exercises/practice/dot-dsl/DotDsl.ps1 b/exercises/practice/dot-dsl/DotDsl.ps1 new file mode 100644 index 0000000..9c09621 --- /dev/null +++ b/exercises/practice/dot-dsl/DotDsl.ps1 @@ -0,0 +1,56 @@ +<# +.SYNOPSIS + Write a Domain Specific Language similar to the Graphviz dot language. + +.DESCRIPTION + Implement the classes to stimulate a DSL similar to the Graphviz dot language. + + Node class : represent the nodes inside the graph. + Edge class : represent the relationship between two nodes. + Attr class : represent the attributes of other objects (node, edge or graph). + Graph class: represent the graph, contains info about nodes, edges and attributes. + + Node, Edge and Attr should have the 'Equals' method implemented for the purpose of comparison in the test suite. +#> + +Class Node { + Node(){ + Throw "Please implement this class" + } + + [bool] Equals() { + Throw "Please implement this fucntion" + } +} + +Class Edge { + Edge(){ + Throw "Please implement this class" + } + + [bool] Equals() { + Throw "Please implement this fucntion" + } +} + +Class Attr { + Attr() { + Throw "Please implement this class" + } + + [bool] Equals() { + Throw "Please implement this fucntion" + } +} + +Class Graph { + [Node[]] $Nodes + [Edge[]] $Edges + [Attr[]] $Attrs + + Graph($data){ + Throw "Please implement this class" + } +} + + diff --git a/exercises/practice/dot-dsl/DotDsl.tests.ps1 b/exercises/practice/dot-dsl/DotDsl.tests.ps1 new file mode 100644 index 0000000..f240bb9 --- /dev/null +++ b/exercises/practice/dot-dsl/DotDsl.tests.ps1 @@ -0,0 +1,111 @@ +BeforeAll { + . "./DotDsl.ps1" +} + +Describe "DotDsl test cases" { + It "empty graph" { + $graph = [Graph]::new() + + $graph.Nodes | Should -BeNullOrEmpty + $graph.Edges | Should -BeNullOrEmpty + $graph.Attrs | Should -BeNullOrEmpty + } + + It "graph with one node" { + $graph = [Graph]::new( + @( + [Node]::new("a") + ) + ) + + $graph.Nodes | Should -BeExactly @([Node]::new("a")) + $graph.Edges | Should -BeNullOrEmpty + $graph.Attrs | Should -BeNullOrEmpty + } + + It "graph with one node with keywords" { + $graph = [Graph]::new( + @( + [Node]::new("a", @{Color = "green"}) + ) + ) + + $graph.Nodes | Should -BeExactly @([Node]::new("a", @{Color = "green"})) + $graph.Edges | Should -BeNullOrEmpty + $graph.Attrs | Should -BeNullOrEmpty + } + + It "graph with one edge" { + $graph = [Graph]::new( + @( + [Edge]::new("a", "b") + ) + ) + + $graph.Nodes | Should -BeNullOrEmpty + $graph.Edges | Should -BeExactly @([Edge]::new("a", "b")) + $graph.Attrs | Should -BeNullOrEmpty + } + + It "graph with one edge with attribute" { + $graph = [Graph]::new( + @( + [Edge]::new("a", "b", @{Style = "dotted"}) + ) + ) + + $graph.Nodes | Should -BeNullOrEmpty + $graph.Edges | Should -BeExactly @([Edge]::new("a", "b", @{Style = "dotted"})) + $graph.Attrs | Should -BeNullOrEmpty + } + + It "graph with one attribute" { + $graph = [Graph]::new( + @( + [Attr]::new(@{Title = "mygraph"}) + ) + ) + + $graph.Nodes | Should -BeNullOrEmpty + $graph.Edges | Should -BeNullOrEmpty + $graph.Attrs | Should -BeExactly @([Attr]::new(@{Title = "mygraph"})) + } + + It "graph with attributes" { + $graph = [Graph]::new( + @( + [Attr]::new(@{Title = "mygraph"}), + [Attr]::new(@{Author = "exercism"}), + [Node]::new("a", @{Color = "green"}), + [Node]::new("c"), + [Node]::new("b", @{Label = "beta"}), + [Edge]::new("b", "c"), + [Edge]::new("a", "b", @{Style = "solid"; Color = "yellow"}), + [Attr]::new(@{Version = 1.0}) + ) + ) + + $nodes = @( + [Node]::new("a", @{Color = "green"}), + [Node]::new("c"), + [Node]::new("b", @{Label = "beta"}) + ) + $edges = @( + [Edge]::new("b", "c"), + [Edge]::new("a", "b", @{Style = "solid"; Color = "yellow"}) + ) + $attrs = @( + [Attr]::new(@{Title = "mygraph"}), + [Attr]::new(@{Author = "exercism"}), + [Attr]::new(@{Version = 1.0}) + ) + + $graph.Nodes | Should -BeExactly $nodes + $graph.Edges | Should -BeExactly $edges + $graph.Attrs | Should -BeExactly $attrs + } + + It "unknown item in graph throw error" { + {[Graph]::new("hello")} | Should -Throw "*Graph can only contain node, egde or attribute*" + } +}