From a3f2207fc4c411fc83183ef5b1dd35468ab8fbf8 Mon Sep 17 00:00:00 2001 From: Ranjit Jhala Date: Thu, 9 Apr 2020 14:43:02 -0700 Subject: [PATCH] init repo --- LICENSE | 20 ++ Makefile | 25 ++ README.md | 621 +++++++++++++++++++++++++++++++++ cse230-trees.cabal | 74 ++++ img/pin | 1 + out/carpet.png | Bin 0 -> 8081 bytes out/chess1.png | Bin 0 -> 7189 bytes out/chess2.png | Bin 0 -> 7189 bytes out/htree-find.txt | 6 + out/htree-find.txt.log | 6 + out/htree-ls.txt | 9 + out/htree-ls.txt.log | 9 + out/rainbow.png | Bin 0 -> 4615 bytes out/triangle1.png | Bin 0 -> 27691 bytes out/triangle2.png | Bin 0 -> 27691 bytes platform/linux/CSE230/Shift.hs | 9 + platform/mac/CSE230/Shift.hs | 9 + src/CSE230/Directory.hs | 232 ++++++++++++ src/CSE230/Doc.hs | 216 ++++++++++++ src/CSE230/Graphics.hs | 100 ++++++ src/CSE230/List.hs | 94 +++++ src/CSE230/Shapes.hs | 106 ++++++ src/Main.hs | 12 + stack.yaml | 8 + test/Common.hs | 91 +++++ test/Test.hs | 115 ++++++ 26 files changed, 1763 insertions(+) create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 cse230-trees.cabal create mode 100644 img/pin create mode 100644 out/carpet.png create mode 100644 out/chess1.png create mode 100644 out/chess2.png create mode 100644 out/htree-find.txt create mode 100644 out/htree-find.txt.log create mode 100644 out/htree-ls.txt create mode 100644 out/htree-ls.txt.log create mode 100644 out/rainbow.png create mode 100644 out/triangle1.png create mode 100644 out/triangle2.png create mode 100644 platform/linux/CSE230/Shift.hs create mode 100644 platform/mac/CSE230/Shift.hs create mode 100644 src/CSE230/Directory.hs create mode 100644 src/CSE230/Doc.hs create mode 100644 src/CSE230/Graphics.hs create mode 100644 src/CSE230/List.hs create mode 100644 src/CSE230/Shapes.hs create mode 100644 src/Main.hs create mode 100644 stack.yaml create mode 100644 test/Common.hs create mode 100644 test/Test.hs diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a525d89 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2016 Ranjit Jhala + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..84af5be --- /dev/null +++ b/Makefile @@ -0,0 +1,25 @@ +ASSIGNMENT = cse230-trees +ZIPFILE = $(ASSIGNMENT).zip +ZIPCONTENTS = img platform src test out $(ASSIGNMENT).cabal stack.yaml Makefile .gitignore LICENSE README.md + +STACK = stack --allow-different-user + +.PHONY: all test build clean distclean turnin + +# make run silent +.SILENT: + +all: test + +test: clean + $(STACK) test --test-arguments="--num-threads 1" + +clean: + rm -f img/*.png out/*.log + +distclean: clean + $(STACK) clean + +turnin: clean + rm -f $(ZIPFILE) + zip -r $(ZIPFILE) $(ZIPCONTENTS) -x '*/\.*' -x@.gitignore diff --git a/README.md b/README.md new file mode 100644 index 0000000..6c0ca38 --- /dev/null +++ b/README.md @@ -0,0 +1,621 @@ +# Assignment 1: Recursion, Datatypes and Higher Order Functions (400 points) + +## Overview + +The overall objective of this assignment is to get some +experience using the *core* features of functional programming, +namely, Recursion, Datatypes and Higher-Order functions. + +The assignment is in the following files that you will modify + +- [List.hs](/src/CSE230/List.hs) +- [Shapes.hs](/src/CSE230/Shapes.hs) +- [Doc.hs](/src/CSE230/Doc.hs) +- [Directory.hs](/src/CSE230/Directory.hs) + +Finally, there are a`Test.hs` has some sample tests to be used +to check your assignments before submitting. + +- [test/Test.hs](/test/Test.hs) + +You should **only modify** the parts of the files which say: + +```haskell +error "fill this in" +``` + +with suitable Haskell implementations. + +**You are free to write and use any helper functions.** + +## Instructions + +### Assignment Testing and Evaluation + +Your functions/programs **must** compile and run on the [VM][VM-URL]. + +**NOTE:** Please download the [VM][VM-URL] again, we have added some extra libraries for this assignment! + +Most of the points, will be awarded automatically, by +**evaluating your functions against a given test suite**. + +[Tests.hs](/tests/Test.hs) contains a very small suite +of tests which gives you a flavor of of these tests. +When you run + +```shell +$ make test +``` + +Your last lines should have + +``` +All N tests passed (...) +OVERALL SCORE = ... / ... +``` + +**or** + +``` +K out of N tests failed +OVERALL SCORE = ... / ... +``` + +**If your output does not have one of the above your code will receive a zero** + +If for some problem, you cannot get the code to compile, +leave it as is with the `error "fill me in"` with your +partial solution enclosed below as a comment. + +The other lines will give you a readout for each test. +You are encouraged to try to understand the testing code, +but you will not be graded on this. + +### Submission Instructions + +You can submit your code by building `cse230-trees.zip` and uploading via GradeScope. + +1. Build the `cse230-trees.zip` file by running + +```bash +$ make turnin +``` + +2. Submit the `cse230-trees.zip` via GradeScope. + +### Installation Instructions for Ubuntu/Linux Users + +You may have to do the equivalent of + +``` +$ sudo apt-get install libxi-dev libxrandr-dev libxcursor-dev libxinerama-dev freeglut3-dev +``` + +to get the graphics stuff working. (Or just use the [VM image][VM-URL]) + + +### Installation Instructions for WSL Users + +**We strongly recommend using the VM for this assignment!** + +If you're using the Windows Subsystem for Linux in Windows 10, +there's additional steps required to get this code working for you: + + - The required OpenGL libraries are not supported in WSL + out of the box, and so you'll need to install `mesa-utils`. + + - On some distributions that use the apt package manager, + such as `Pengwin`, you can type `sudo apt install mesa-utils` + + - You need to have an X window server installed and running + in Windows 10 as well, even though this assignment doesn't + use it. X410 is easy to use, but it is not free. + Xming is available for free. If WSL can't find the display, + try `export DISPLAY=:0` + + - Superuser priviliges are required within WSL, so instead of + `stack run`, type `sudo stack run --allow-different-user` + +## Problem 1: Lists + +### Cloning + +Fill in the implementation of `clone` such that `clone n x` returns +a list of `n` copies of `x`. When you are done, you should see the +following behavior: + +```haskell +>>> clone 5 'a' +"aaaaa" + +>>> clone 3 "cat" +["cat","cat","cat"] +``` + +### Padding + +Fill in the implementation of `pad` such that `pad dir n x ys` +"pads" the list `ys` with as many copies of `x` as needed to make +it exactly of size `n`. The `dir` parameter specifies whether the +copies of `x` should be added at the beginning (`DirL`) or at the +end (`DirR`). + +When you are done, you should see the following behavior: + +```haskell +>>> pad DirL 10 0 [1,2,3,4,5] +[0,0,0,0,0,1,2,3,4,5] + +>>> pad DirR 10 0 [1,2,3,4,5] +[1,2,3,4,5,0,0,0,0,0] + +>>> pad DirL 3 0 [1,2,3,4,5] +[1,2,3,4,5] + +>>> pad DirR 3 0 [1,2,3,4,5] +[1,2,3,4,5] +``` + +### Sub-Sequence + +Fill in the definition of `isSubsequence` such that +`isSubSequence s1 s2` returns True if `s1` can be +obtained by *deleting* some elements of `s2`. + +When you are done, you should see the following behavior: + +```haskell +>>> isSubSequence "cat" "dog" +False + +>>> isSubSequence "cat" "craptasticdog" +True +``` + +### Maximum + +Fill in the implementation of `maximum` so that `maximum d xs` +returns the largest of `d:xs`. + +When you are done, you should see the following behavior: + +```haskell +>>> maximum 99 [] +99 + +>>> maximum 99 [90, 100, 200, 52] +200 +``` + +### Intersperse + +Fill in the definition of `intersp` such that +`intersp s [x1,x2,...,xn]` returns the list +`[x1, s, x2, s, ..., xn, s]`. + +When you are done, you should see the following behavior: + +```haskell +>>> intersp '|' "chewbacca" +"|c|h|e|w|b|a|c|c|a|" + +>>> intersp "yo!" ["which", "way", "is", "the", "park"] +["yo!","which","yo!","way","yo!","is","yo!","the","yo!","park","yo!"] +``` + +### `iter` + +Fill in the definition of `iter` such that `iter n f x` +returns the result of calling `f` on `x` exactly `n` times, e.g. + +- `iter 3 f x` returns `f (f (f x))`, and +- `iter 5 f x` returns `f (f (f (f (f x))))`. + +When you are done you should get the following behavior: + +```haskell +>>> iter 10 (\x -> 2 * x) 1 +1024 +``` + +## Problem 2: Shapes + +### Rainbow + +Fill in the implementation of `rainbow` so that when you +are done, running + +```haskell +>>> mkRainbow +``` + +creates a file `img/rainbow.png` that looks identical to + +![Rainbow](/out/rainbow.png) + +**HINT:** Read the documentation for `overlay` and `circle` from +the [`Graphics.Htdp` library](http://hackage.haskell.org/package/htdp-image-1.1.0.0/docs/Graphics-Htdp.html). + +### ChessBoard using `iter` + +Fill in the implementation of `chessBoard2` so that when you +are done, running + +```haskell +>>> mkChess2 +``` + +creates a file `img/chess2.png` that looks identical to + +![Chess Board](/out/chess2.png) + +**HINT:** Make sure you understand the API in `chessBoard1`. + +### Triangle using recursion + +Fill in the implementation of `triRec` so that when you +are done, running + +```haskell +>>> mkTriangle1 +``` + +creates a file `img/triangle1.png` that looks identical to + +![Chess Board](/out/triangle1.png) + +**HINT:** You may find the `Graphics.Htdp` functions `above` and `beside` useful. + +### Triangle using `iter` + +Fill in the implementation of `sierpinskiTriangle2` so that when you +are done, running + +```haskell +>>> mkTriangle2 +``` + +creates a file `img/triangle2.png` that looks identical to + +![Chess Board](/out/triangle2.png) + +**HINT:** Make sure you understand the relation between `iter` and recursion. + +### Carpet with `iter` + +Fill in the implementation of `sierpinskiCarpet` so that when you +are done, running + +```haskell +>>> mkCarpet +``` + +creates a file `img/carpet.png` that looks identical to + +![Chess Board](/out/carpet.png) + +## Problem 3: Documents + +For this problem you will write a simple document layout engine, that allows the pretty printing of nested documents. + +Thus, a document is a list of lines, each of which is a string. For example, the document + +``` +a +aa +aaa +aaaa +``` + +is represented as + +```haskell +>>> D ["a", "aa", "aaa", "aaaa"] +``` + +We have also provided implementations of methods for computing + +* the `height` of a document (the number of lines) +* the `width` of a document (the maximum number of characters in a line) + +### Vertical Concatenation aligned at Left + +Fill in the implementation of `vcatL` such that `vcatL d1 d2` +*vertically concatenates* the documents `d1` and `d2` aligning +their *left* sides. + +When you are done, you should see the following behavior: + +```haskell +>>> (doc "cat") `vcatL` (doc "horse") `vcatL` (doc "mongoose") +cat +horse +mongoose +``` + +### Vertical Concatenation aligned at Right + +Fill in the implementation of `vcatR` such that `vcatR d1 d2` +*vertically concatenates* the documents `d1` and `d2` aligning +their *right* sides. + +When you are done, you should see the following behavior: + +```haskell +>>> (doc "cat") `vcatR` (doc "horse") `vcatR` (doc "mongoose") + cat + horse +mongoose +``` + + + +### Horizontal Concatenation aligned at Top + +Fill in the implementation of `hcatT` such that `hcatT d1 d2` +*horizontally concatenates* the documents `d1` and `d2` aligning +their *top* sides. + + +Suppose you have the following `Doc` values: + +```haskell +>>> aDoc +a +aaa +aaaaa +``` + +```haskell +>>> bDoc +b +bbb +``` + +```haskell +>>> lineDoc +<----- HERE +``` + +When you are done with `hcatT`, you should see the following behavior: + +```haskell +>>> hcatT aDoc lineDoc +a <----- HERE +aaa +aaaaa +``` + +and + +```haskell +>>> hcatT aDoc bDoc +a b +aaa bbb +aaaaa +``` + + +### Horizontal Concatenation aligned at Bottom + +Fill in the implementation of `hcatB` such that `hcatB d1 d2` +*horizontally concatenates* the documents `d1` and `d2` aligning +their *bottom* sides. + + +When you are done you should see the following behavior: + +```haskell +>>> hcatB aDoc lineDoc +a +aaa +aaaaa<----- HERE +``` + +and + +```haskell +>>> hcatB aDoc bDoc +a +aaa b +aaaaabbb +``` + +## Problem 4: `htree` + +Finally, we will use `Doc` to build a command-line tool +called `htree` which does two tasks. + +*1. Showing the Sub-Directories* + +At the shell, executing + +``` +$ htree -ls src +``` + +produces the following tree-representation of the `src` +directory (of this repo). + +``` +src +├── CSE230 +│ ├── Doc.hs +│ ├── Directory.hs +│ ├── List.hs +│ ├── Shapes.hs +│ └── Graphics.hs +└── Main.hs +``` + +*2. Finding Files Matching a Pattern* + +At the shell, executing + +``` +$ htree -find src .hs +``` + +prints out the list of all the files (recursively) inside `src` +that match the substring `.hs`: + +``` +src/CSE230/Doc.hs +src/CSE230/Directory.hs +src/CSE230/List.hs +src/CSE230/Shapes.hs +src/CSE230/Graphics.hs +src/Main.hs +``` + +### Directories + +We represent a *directory* via a datatype + +```haskell +data Dir a + = Fil a -- ^ A single file named `a` + | Sub a [Dir a] -- ^ A sub-directory name `a` with contents `[Dir a]` +``` + +For example, the files in the `src` directory can be represented as: + +```haskell +srcDir :: Dir FilePath +srcDir = Sub "src" + [ Sub "CSE230" + [ Fil "Doc.hs" + , Fil "Directory.hs" + , Fil "List.hs" + , Fil "Shapes.hs" + , Fil "Graphics.hs" + ] + , Fil "Main.hs" + ] +``` + +### `dirDoc` + +Fill in the implementation of `dirDoc` that converts a `Dir FilePath`, +i.e. a directory where each name is a `FilePath` (i.e. `String`) into +a `Doc` value (from the previous problem). Your code should only use +the *exported* functions of the `Doc` module, i.e. + +* the `doc` constructor, and +* the combinators `hcatT`, `hcatB`, `vcatL` and `vcatR`. + +**HINT:** You may find `dash`, `stile` , `angle` and `bar` useful. + +When you are done, you should see the following behavior: + +```haskell +>>> dirDoc srcDir +src +├── CSE230 +│ ├── Doc.hs +│ ├── Directory.hs +│ ├── List.hs +│ ├── Shapes.hs +│ └── Graphics.hs +└── Main.hs +``` + +```haskell +>>> dirDoc example +. +├── LICENSE +├── README.md +├── cse230-tree.cabal +├── img +│ ├── carpet.png +│ ├── chess1.png +│ ├── chess2.png +│ ├── rainbow.png +│ ├── triangle1.png +│ └── triangle2.png +├── src +│ ├── Main.hs +│ └── One +│ ├── Directory.hs +│ ├── Doc.hs +│ ├── Graphics.hs +│ ├── List.hs +│ └── Shapes.hs +└── stack.yaml +``` + +### `allFiles` + +Understand and use `foldDir` to fill in the implementation +of `allFiles dir` which returns a list of *all the files* +in the directory `dir`. + +When you are done, you should see the following behavior: + +```haskell +>>> allFiles example +["LICENSE","README.md","cse230-tree.cabal","carpet.png","chess1.png","chess2.png","rainbow.png","triangle1.png","triangle2.png","Main.hs","Directory.hs","Doc.hs","Graphics.hs","List.hs","Shapes.hs","stack.yaml"] +``` + + +### `allDirs` + +Understand and use `foldDir` to fill in the implementation of +`allDirs dir` which returns a list of *all the sub-directories* +in the directory `dir`. + +When you are done, you should see the following behavior: + +```haskell +>>> allDirs example +[".","img","src","One"] +``` + + +### `findFiles` + +Understand and use `foldDir` to fill in the implementation of +`findFiles sub dir` which returns a list of *all the files* +in the directory `dir` such that `sub` is a *subsequence* +of the files' names. + +When you are done, you should see the following behavior: + +```haskell +>>> findFiles ".hs" example +["./src/Main.hs","./src/One/Directory.hs","./src/One/Doc.hs","./src/One/Graphics.hs","./src/One/List.hs","./src/One/Shapes.hs"] +``` + +### `build` + +Finally, complete the implementation of the function +`build path` that recursively traverses the filesystem +starting at `path` to build the `Dir FilePath` object +describing the filesystem's contents at `path`. (You +can ignore complexities like symbolic links etc.) + +When you are done, you should see the following +behavior (assuming you have not added extra files +in your `src/` directory!) + +```haskell +>>> build "src" +Sub "src" [Sub "CSE230" [Fil "Doc.hs",Fil "Directory.hs",Fil "List.hs",Fil "Shapes.hs",Fil "Graphics.hs"],Fil "Main.hs"] +``` + +Finally, at this point, `stack install` should build and +install a standalone executable `htree` that you can then +run as described above: + +``` +$ htree -ls src +``` + +and + +``` +$ htree -find src .hs +``` + + +[VM-URL]: https://drive.google.com/file/d/1BlYeSZPNVrxUu8jQWgUkquRBjE6wQww3/view?usp=sharing \ No newline at end of file diff --git a/cse230-trees.cabal b/cse230-trees.cabal new file mode 100644 index 0000000..f71ffc1 --- /dev/null +++ b/cse230-trees.cabal @@ -0,0 +1,74 @@ +name: cse230-trees +version: 0.1.0.0 +synopsis: Starter code +license: MIT +license-file: LICENSE +author: Ranjit Jhala +maintainer: jhala@cs.ucsd.edu +category: Language +build-type: Simple +extra-source-files: README.md +cabal-version: >=1.10 + + +Library + ghc-options: -W + exposed-modules: CSE230.Shift, + CSE230.Graphics, + CSE230.List, + CSE230.Shapes, + CSE230.Doc, + CSE230.Directory + + Default-Extensions: OverloadedStrings + + -- other-extensions: + build-depends: base, + filepath, + directory, + QuickCheck, + gloss, + gloss-export, + JuicyPixels, + htdp-image + + hs-source-dirs: src + if os(darwin) + hs-source-dirs: platform/mac + if !os(darwin) + hs-source-dirs: platform/linux + + default-language: Haskell2010 + +executable htree + build-depends: base, + filepath, + directory, + QuickCheck, + JuicyPixels, + gloss, + gloss-export, + htdp-image, + bmp, + cse230-trees + + default-language: Haskell2010 + main-is: src/Main.hs + Default-Extensions: OverloadedStrings + +test-suite test + default-language: Haskell98 + type: exitcode-stdio-1.0 + hs-source-dirs: test + Default-Extensions: OverloadedStrings + main-is: Test.hs + build-depends: base, + filepath, + directory, + QuickCheck, + tasty, + tasty-hunit, + tasty-quickcheck, + process, + cse230-trees + other-modules: Common diff --git a/img/pin b/img/pin new file mode 100644 index 0000000..1cbbd8d --- /dev/null +++ b/img/pin @@ -0,0 +1 @@ +dummy file for git diff --git a/out/carpet.png b/out/carpet.png new file mode 100644 index 0000000000000000000000000000000000000000..f81f50fb69b2135ba6cab824246d1b75723da696 GIT binary patch literal 8081 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 literal 0 HcmV?d00001 diff --git a/out/chess1.png b/out/chess1.png new file mode 100644 index 0000000000000000000000000000000000000000..ef45c27b98ac34a3821017db70e04b0171e2a40d GIT binary patch literal 7189 zcmeAS@N?(olHy`uVBq!ia0y~yV4MiT9Be?5C5nCF3=GnDJY5_^D(1YMY}mzW$aD1B zzv=(D-(At;XK`fuzEPxpe@910{imD!5ua-F&uHI}d+nIh|5ahz@tZf4roS+<7D}jI z!@kk>TJsIL^yKcCMW^3*G`!s?cjQ|He^T|FUfrTjz0Jyu+m2TW+)lRbcw1<;?S$&C zGkq+h291W%XgVT4i+sLhv>uW5UOVQAMyCaKz;oUj_MM?QGhG$o8E>Q9ldz4cVy%dL zS2ZE}=(I?YBzXQEHRSkcn2e?qV1B5~5d9zgWhWv#jaFo%mHlWtX5iX-`nsRFMyoPJ zc{EzR1N$MPO&;pB;M&7J+Plv0Mzjn^yLO|!*3q8*fDKxVcI`%cs-wMk8ddM3UAxiY u*U=&BLDbe8?b?lw+>Q=J(`azse%Y%@OZPpT$^o42WAJqKb6Mw<&;$TndCuGb literal 0 HcmV?d00001 diff --git a/out/chess2.png b/out/chess2.png new file mode 100644 index 0000000000000000000000000000000000000000..ef45c27b98ac34a3821017db70e04b0171e2a40d GIT binary patch literal 7189 zcmeAS@N?(olHy`uVBq!ia0y~yV4MiT9Be?5C5nCF3=GnDJY5_^D(1YMY}mzW$aD1B zzv=(D-(At;XK`fuzEPxpe@910{imD!5ua-F&uHI}d+nIh|5ahz@tZf4roS+<7D}jI z!@kk>TJsIL^yKcCMW^3*G`!s?cjQ|He^T|FUfrTjz0Jyu+m2TW+)lRbcw1<;?S$&C zGkq+h291W%XgVT4i+sLhv>uW5UOVQAMyCaKz;oUj_MM?QGhG$o8E>Q9ldz4cVy%dL zS2ZE}=(I?YBzXQEHRSkcn2e?qV1B5~5d9zgWhWv#jaFo%mHlWtX5iX-`nsRFMyoPJ zc{EzR1N$MPO&;pB;M&7J+Plv0Mzjn^yLO|!*3q8*fDKxVcI`%cs-wMk8ddM3UAxiY u*U=&BLDbe8?b?lw+>Q=J(`azse%Y%@OZPpT$^o42WAJqKb6Mw<&;$TndCuGb literal 0 HcmV?d00001 diff --git a/out/htree-find.txt b/out/htree-find.txt new file mode 100644 index 0000000..61712b6 --- /dev/null +++ b/out/htree-find.txt @@ -0,0 +1,6 @@ +src/CSE230/Directory.hs +src/CSE230/Doc.hs +src/CSE230/Graphics.hs +src/CSE230/List.hs +src/CSE230/Shapes.hs +src/Main.hs diff --git a/out/htree-find.txt.log b/out/htree-find.txt.log new file mode 100644 index 0000000..61712b6 --- /dev/null +++ b/out/htree-find.txt.log @@ -0,0 +1,6 @@ +src/CSE230/Directory.hs +src/CSE230/Doc.hs +src/CSE230/Graphics.hs +src/CSE230/List.hs +src/CSE230/Shapes.hs +src/Main.hs diff --git a/out/htree-ls.txt b/out/htree-ls.txt new file mode 100644 index 0000000..25a7387 --- /dev/null +++ b/out/htree-ls.txt @@ -0,0 +1,9 @@ +src +├── CSE230 +│ ├── Directory.hs +│ ├── Doc.hs +│ ├── Graphics.hs +│ ├── List.hs +│ └── Shapes.hs +└── Main.hs + diff --git a/out/htree-ls.txt.log b/out/htree-ls.txt.log new file mode 100644 index 0000000..25a7387 --- /dev/null +++ b/out/htree-ls.txt.log @@ -0,0 +1,9 @@ +src +├── CSE230 +│ ├── Directory.hs +│ ├── Doc.hs +│ ├── Graphics.hs +│ ├── List.hs +│ └── Shapes.hs +└── Main.hs + diff --git a/out/rainbow.png b/out/rainbow.png new file mode 100644 index 0000000000000000000000000000000000000000..6d047b8b04404f190ba10f09fc8226667dc27220 GIT binary patch literal 4615 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 I&z_k6Kbts2v;Y7A literal 0 HcmV?d00001 diff --git a/out/triangle1.png b/out/triangle1.png new file mode 100644 index 0000000000000000000000000000000000000000..c734bf6cd690189f80bab3270eb75a3eba70fff2 GIT binary patch literal 27691 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 Iw-{gif7XfZq5uE@ literal 0 HcmV?d00001 diff --git a/out/triangle2.png b/out/triangle2.png new file mode 100644 index 0000000000000000000000000000000000000000..c734bf6cd690189f80bab3270eb75a3eba70fff2 GIT binary patch literal 27691 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 Iw-{gif7XfZq5uE@ literal 0 HcmV?d00001 diff --git a/platform/linux/CSE230/Shift.hs b/platform/linux/CSE230/Shift.hs new file mode 100644 index 0000000..7ccdbff --- /dev/null +++ b/platform/linux/CSE230/Shift.hs @@ -0,0 +1,9 @@ +module CSE230.Shift where + +import qualified Graphics.Gloss as G + +shift :: Float -> (Float, Float) -> (Float, Float) +shift _ (x, y) = (x, y) + +scale :: G.Picture -> G.Picture +scale = id diff --git a/platform/mac/CSE230/Shift.hs b/platform/mac/CSE230/Shift.hs new file mode 100644 index 0000000..35cb7e3 --- /dev/null +++ b/platform/mac/CSE230/Shift.hs @@ -0,0 +1,9 @@ +module CSE230.Shift where + +import qualified Graphics.Gloss as G + +shift :: Float -> (Float, Float) -> (Float, Float) +shift dim (x, y) = (x - (dim / 2), y + (dim / 2)) + +scale :: G.Picture -> G.Picture +scale p = G.scale 0.5 0.5 p diff --git a/src/CSE230/Directory.hs b/src/CSE230/Directory.hs new file mode 100644 index 0000000..7c48f35 --- /dev/null +++ b/src/CSE230/Directory.hs @@ -0,0 +1,232 @@ +module CSE230.Directory where + +import CSE230.Doc +import CSE230.List hiding (Dir) +import qualified Data.List as L +import System.FilePath (takeDirectory, takeFileName, ()) +import System.Directory (doesFileExist, listDirectory) + +------------------------------------------------------------------------------- +-- | Top-level "main" function +------------------------------------------------------------------------------- +main :: [String] -> IO () +main ["-ls", p] = showDirectory (normalize p) +main ["-find", p, sub] = showMatchingFiles (normalize p) sub +main args = putStrLn (errorMsg args) + +normalize :: FilePath -> FilePath +normalize p = takeDirectory (p ".") + +errorMsg :: [String] -> String +errorMsg args = unlines + [ "Sorry, don't understand: " ++ unwords args ++ ". Try one of" + , " * 'htree -ls ' to render the directory of all files under " + , " * 'htree -find sub' to find all files matching 'sub' under " + ] + +------------------------------------------------------------------------------- +-- | 'showDirectory path' builds and displays the 'Dir' rooted at 'path' +------------------------------------------------------------------------------- +-- >>> showDirectory "src" +-- src +-- ├── CSE230 +-- │ ├── Doc.hs +-- │ ├── Directory.hs +-- │ ├── List.hs +-- │ ├── Shapes.hs +-- │ └── Graphics.hs +-- └── Main.hs +-- +-- + +showDirectory :: FilePath -> IO () +showDirectory path = do + dir <- build path + print (dirDoc dir) + +------------------------------------------------------------------------------- +-- | 'showMatchingFiles path sub' finds the files matching 'sub' (deep) in the +-- the directory rooted at 'path' and prints them out. +------------------------------------------------------------------------------- +-- >>> showMatchingFiles "src" ".hs" +-- src/CSE230/Doc.hs +-- src/CSE230/Directory.hs +-- src/CSE230/List.hs +-- src/CSE230/Shapes.hs +-- src/CSE230/Graphics.hs +-- src/Main.hs + +showMatchingFiles :: FilePath -> String -> IO () +showMatchingFiles path sub = do + dir <- build path + mapM_ putStrLn (findFiles sub dir) + + +------------------------------------------------------------------------------- +-- | The 'Directory' data type +------------------------------------------------------------------------------- +data Dir a + = Fil a -- ^ A single file named `a` + | Sub a [Dir a] -- ^ A sub-directory name `a` with contents `[Dir a]` + deriving (Eq, Show) + +example :: Dir FilePath +example = Sub "." + [ Fil "LICENSE" + , Fil "README.md" + , Fil "cse230-tree.cabal" + , Sub "out" [ Fil "carpet.png" + , Fil "chess1.png" + , Fil "chess2.png" + , Fil "rainbow.png" + , Fil "triangle1.png" + , Fil "triangle2.png" + ] + , Sub "src" [ Fil "Main.hs" + , Sub "CSE230" [ Fil "Directory.hs" + , Fil "Doc.hs" + , Fil "Graphics.hs" + , Fil "List.hs" + , Fil "Shapes.hs" + ] + ] + , Fil "stack.yaml" + ] + +srcDir :: Dir FilePath +srcDir = Sub "src" + [ Sub "CSE230" + [ Fil "Doc.hs" + , Fil "Directory.hs" + , Fil "List.hs" + , Fil "Shapes.hs" + , Fil "Graphics.hs" + ] + , Fil "Main.hs" + ] + + + +------------------------------------------------------------------------------- +-- | Printing Directories +------------------------------------------------------------------------------- + +-- >>> dirDoc example +-- . +-- ├── LICENSE +-- ├── README.md +-- ├── cse230-tree.cabal +-- ├── img +-- │ ├── carpet.png +-- │ ├── chess1.png +-- │ ├── chess2.png +-- │ ├── rainbow.png +-- │ ├── triangle1.png +-- │ └── triangle2.png +-- ├── src +-- │ ├── Main.hs +-- │ └── CSE230 +-- │ ├── Directory.hs +-- │ ├── Doc.hs +-- │ ├── Graphics.hs +-- │ ├── List.hs +-- │ └── Shapes.hs +-- └── stack.yaml +-- +-- >>> dirDoc srcDir +-- src +-- ├── CSE230 +-- │ ├── Doc.hs +-- │ ├── Directory.hs +-- │ ├── List.hs +-- │ ├── Shapes.hs +-- │ └── Graphics.hs +-- └── Main.hs +-- +-- +dirDoc :: Dir FilePath -> Doc +dirDoc (Fil f) = error "fill this in" +dirDoc (Sub f ds) = error "fill this in" + + +------------------------------------------------------------------------------- +-- | Some useful 'Doc's-------------------------------------------------------- +------------------------------------------------------------------------------- +dash :: Doc +dash = doc "── " + +stile :: Doc +stile = doc "├" + +angle :: Doc +angle = doc "└" + +bar :: Doc +bar = doc "│" + +------------------------------------------------------------------------------- +-- | A 'fold' for directories +------------------------------------------------------------------------------- +data DirElem a = SubDir a | File a + +foldDir :: ([a] -> r -> DirElem a -> r) -> r -> Dir a -> r +foldDir f = go [] + where + go stk r (Fil a) = f stk r (File a) + go stk r (Sub a ds) = L.foldl' (go stk') r' ds + where + r' = f stk r (SubDir a) + stk' = a:stk + +------------------------------------------------------------------------------- +-- | 'allFiles dir' returns a list of all the Files in 'dir' +------------------------------------------------------------------------------- +-- >>> allFiles example +-- ["LICENSE","README.md","cse230-tree.cabal","carpet.png","chess1.png","chess2.png","rainbow.png","triangle1.png","triangle2.png","Main.hs","Directory.hs","Doc.hs","Graphics.hs","List.hs","Shapes.hs","stack.yaml"] + +allFiles :: Dir FilePath -> [FilePath] +allFiles dir = reverse (foldDir f [] dir) + where + f = error "fill this in" + + +------------------------------------------------------------------------------- +-- | 'allDirs dir' returns a list of all the sub-directories in 'dir' +------------------------------------------------------------------------------- +-- +-- >>> allDirs example +-- [".","img","src","CSE230"] + +allDirs :: Dir FilePath -> [FilePath] +allDirs dir = reverse (foldDir f [] dir) + where + f = error "fill this in" + + +------------------------------------------------------------------------------- +-- | 'findFiles sub dir' returns a list of all the Files-with-paths in 'dir' +-- such that 'sub' is a substring of the Files' names. +------------------------------------------------------------------------------- +-- +-- >>> findFiles ".hs" example +-- ["./src/Main.hs","./src/CSE230/Directory.hs","./src/CSE230/Doc.hs","./src/CSE230/Graphics.hs","./src/CSE230/List.hs","./src/CSE230/Shapes.hs"] +-- +findFiles :: String -> Dir FilePath -> [FilePath] +findFiles sub dir = reverse (foldDir f [] dir) + where + f = error "fill this in" + + +------------------------------------------------------------------------------- +-- | 'build path' constructing the Directory on the filesystem rooted at 'path' +------------------------------------------------------------------------------- +-- +-- >>> build "src" +-- Sub "src" [Sub "CSE230" [Fil "Doc.hs",Fil "Directory.hs",Fil "List.hs",Fil "Shapes.hs",Fil "Graphics.hs"],Fil "Main.hs"] +-- + +build :: FilePath -> IO (Dir FilePath) +build path = error "fill this in" + +lshow :: Doc -> [String] +lshow = lines . show diff --git a/src/CSE230/Doc.hs b/src/CSE230/Doc.hs new file mode 100644 index 0000000..a17a493 --- /dev/null +++ b/src/CSE230/Doc.hs @@ -0,0 +1,216 @@ +module CSE230.Doc + ( + -- * A document type + Doc + + -- * Constructors + , empty, doc + + -- * Accessors + , width, height + + -- * Example Doc + , aDoc, bDoc, lineDoc, animals, triangles + + -- * Combinators + , vcatL, vcatR, hcatB, hcatT + + -- * Properties + , prop_hcatT_width + , prop_hcatT_height + , prop_hcatB_width + , prop_hcatB_height + , prop_vcatL_width + , prop_vcatR_width + , prop_vcatL_height + , prop_vcatR_height + + ) where + +import qualified Test.QuickCheck as QC +import Prelude hiding (maximum) +import CSE230.List + +------------------------------------------------------------------------------- +-- | A 'Doc' is a 'String' list +------------------------------------------------------------------------------- +data Doc = D { docLines :: [String] } + deriving (Eq, Ord) + +empty :: Doc +empty = D [] + +doc :: String -> Doc +doc s = D (lines s) + +aDoc :: Doc +aDoc = D [ "a" + , "aaa" + , "aaaaa"] + +bDoc :: Doc +bDoc = D [ "b" + , "bbb"] + +lineDoc :: Doc +lineDoc = doc "<----- HERE" + +animals :: [Doc] +animals = [ doc "cat" + , doc "horse" + , doc "mongoose" + ] + +------------------------------------------------------------------------------- +-- | Printing a Doc +------------------------------------------------------------------------------- +-- >>> aDoc +-- a +-- aaa +-- aaaaa +-- +-- + +instance Show Doc where + show (D ls) = unlines ls + +------------------------------------------------------------------------------- +-- | Generating Random Docs +------------------------------------------------------------------------------- +instance QC.Arbitrary Doc where + arbitrary = fmap D QC.arbitrary + +------------------------------------------------------------------------------- +-- | Dimensions of a 'Doc' +------------------------------------------------------------------------------- +-- >>> width beers +-- 11 + +width :: Doc -> Int +width (D ls) = maximum 0 (map length ls) + +-- >>> height beers +-- 3 + +height :: Doc -> Int +height (D ls) = length ls + +------------------------------------------------------------------------------- +-- | Vertical Concatenation aligned at Left +------------------------------------------------------------------------------- + +-- >>> (doc "cat") `vcatL` (doc "horse") `vcatL` (doc "mongoose") +-- cat +-- horse +-- mongoose +-- +-- + +vcatL :: Doc -> Doc -> Doc +vcatL d1 d2 = error "fill this in" + +------------------------------------------------------------------------------- +-- | Vertical Concatenation aligned at Right +------------------------------------------------------------------------------- + +-- >>> (doc "cat") `vcatR` (doc "horse") `vcatR` (doc "mongoose") +-- cat +-- horse +-- mongoose +-- +-- + +vcatR :: Doc -> Doc -> Doc +vcatR d1 d2 = error "fill this in" + +------------------------------------------------------------------------------- +-- | Horizontal Concatenation aligned at Top +-- HINT: use `zip` or `zipWith` +------------------------------------------------------------------------------- + +-- >>> hcatT aDoc lineDoc +-- a <----- HERE +-- aaa +-- aaaaa +-- +-- +-- >>> hcatT aDoc bDoc +-- a b +-- aaa bbb +-- aaaaa +-- +-- +hcatT :: Doc -> Doc -> Doc +hcatT d1 d2 = error "fill this in" + +elongate :: Dir -> Int -> Doc -> Doc +elongate dir h (D ls) = D (pad dir h "" ls) + +------------------------------------------------------------------------------- +-- | Horizontal Concatenation aligned at Bottom +-- HINT: use `zip` or `zipWith` +------------------------------------------------------------------------------- +-- >>> hcatB aDoc lineDoc +-- a +-- aaa +-- aaaaa<----- HERE +-- +-- +-- >>> hcatB aDoc bDoc +-- a +-- aaa b +-- aaaaabbb +-- +-- +hcatB :: Doc -> Doc -> Doc +hcatB d1 d2 = error "fill this in" + +triangle :: Doc +triangle = D + [ "*" + , "***" + , "*****" ] + +-- >>> foldr vcatL empty triangles +-- * +-- *** +-- ***** +-- * * +-- *** *** +-- ********** +-- * * * +-- *** *** *** +-- *************** +-- +-- + +-- >>> foldr vcatR empty triangles +-- * +-- *** +-- ***** +-- * * +-- *** *** +-- ********** +-- * * * +-- *** *** *** +-- *************** +-- +-- +triangles :: [Doc] +triangles = [ triangle + , triangle `hcatT` triangle + , triangle `hcatT` triangle `hcatT` triangle ] + + +------------------------------------------------------------------------------- +-- | Properties of `Doc` combinators ------------------------------------------ +------------------------------------------------------------------------------- + +prop_hcatT_width d1 d2 = width (d1 `hcatT` d2) == width d1 + width d2 +prop_hcatT_height d1 d2 = height (d1 `hcatT` d2) == max (height d1) (height d2) +prop_hcatB_width d1 d2 = width (d1 `hcatB` d2) == width d1 + width d2 +prop_hcatB_height d1 d2 = height (d1 `hcatB` d2) == max (height d1) (height d2) +prop_vcatL_width d1 d2 = width (d1 `vcatL` d2) == max (width d1) (width d2) +prop_vcatR_width d1 d2 = width (d1 `vcatR` d2) == max (width d1) (width d2) +prop_vcatL_height d1 d2 = height (d1 `vcatL` d2) == height d1 + height d2 +prop_vcatR_height d1 d2 = height (d1 `vcatR` d2) == height d1 + height d2 diff --git a/src/CSE230/Graphics.hs b/src/CSE230/Graphics.hs new file mode 100644 index 0000000..9936de0 --- /dev/null +++ b/src/CSE230/Graphics.hs @@ -0,0 +1,100 @@ +module CSE230.Graphics where + +import Graphics.Htdp +import qualified Graphics.Gloss.Export as G +import qualified Graphics.Gloss as G +import qualified Codec.Picture as J +import Graphics.Htdp.Data.Image (Image (..)) +import CSE230.Shift + +------------------------------------------------------------------------------- +-- Save a 'Image' as a PNG file +------------------------------------------------------------------------------- +save :: FilePath -> Image -> IO () +save f raw = G.exportPictureToPNG (siz, siz) bgCol f (scale pic) + where + pic = G.Pictures [ G.translate x y p + | (p, xy) <- shapes img + , let (x, y) = shift dim xy + ] + siz = succ . round $ dim + img = overlay raw (square dim solid bgCol) + dim = max (width raw) (height raw) + +{- +savePNG_vm :: FilePath -> Image -> IO () +savePNG_vm f raw = G.exportPictureToPNG (h, w) bgCol f pic + where + i = overlay raw (square dim solid bgCol) + pic = G.Pictures (map (\(p, (x, y)) -> G.translate x y p) (shapes i)) + w = succ . round $ wi + h = succ . round $ hi + wi = width i + hi = height i + dim = max (width raw) (height raw) + +savePNG2 :: FilePath -> Image -> IO () +savePNG2 f raw = + G.exportPictureToPNG (h, w) bgCol f pic + where + i = overlay raw (square dim solid bgCol) + pic = G.Pictures (map (\(p, (x, y)) -> G.translate x y p) (shapes i)) + w = succ . round $ wi + h = succ . round $ hi + wi = width i + hi = height i + dim = max (width raw) (height raw) +-} + +------------------------------------------------------------------------------- +-- | Foreground color +------------------------------------------------------------------------------- +fgCol :: Color +fgCol = blue + +------------------------------------------------------------------------------- +-- | Background color +------------------------------------------------------------------------------- +bgCol :: Color +bgCol = white + +------------------------------------------------------------------------------- +compareBMP :: Int -> FilePath -> FilePath -> IO Bool +------------------------------------------------------------------------------- +compareBMP thresh path1 path2 = do + bmp1E <- J.readBitmap path1 + bmp2E <- J.readBitmap path2 + case (bmp1E, bmp2E) of + (Left e1, _) -> fileError path1 e1 >> return False + (_ , Left e2) -> fileError path2 e2 >> return False + (Right b1, Right b2) -> do let pct = cmpImage (J.convertRGB8 b1) (J.convertRGB8 b2) + putStrLn $ "Image difference: " ++ show (path1, path2, pct) + return (pct <= thresh) + +fileError :: FilePath -> String -> IO () +fileError path e = putStrLn $ "Cannot read " ++ path ++ " due to error " ++ e + +cmpImage :: J.Image J.PixelRGB8 -> J.Image J.PixelRGB8 -> Int +cmpImage i1 i2 + | w1 == w2 && h1 == h2 = (100 * absDiff) `div` picSize + | otherwise = 100 + where + w1 = J.imageWidth i1 + w2 = J.imageWidth i2 + h1 = J.imageWidth i1 + h2 = J.imageWidth i2 + absDiff = (sum [pixelDiff (J.pixelAt i1 x y) (J.pixelAt i2 x y) | x <- [0..w1-1], y <- [0..h1-1]]) + picSize = (3 * 255 * w1 * h1) + +pixelDiff :: J.PixelRGB8 -> J.PixelRGB8 -> Int +pixelDiff (J.PixelRGB8 r1 g1 b1) (J.PixelRGB8 r2 g2 b2) = fromIntegral $ abs (r1 - r2) + abs (g1 - g2) + abs (b1 - b2) + +readImage :: FilePath -> IO [J.PixelRGB8] +readImage path = do + bE <- J.readBitmap path + case bE of + Left _ -> error "Oh no" + Right b -> return [ J.pixelAt i x y | x <- [0..w-1], y <- [0..h-1]] + where i = J.convertRGB8 b + h = J.imageHeight i + w = J.imageWidth i \ No newline at end of file diff --git a/src/CSE230/List.hs b/src/CSE230/List.hs new file mode 100644 index 0000000..1195e9f --- /dev/null +++ b/src/CSE230/List.hs @@ -0,0 +1,94 @@ +module CSE230.List where + +import Prelude hiding (maximum) + +------------------------------------------------------------------------------- +-- | clone +------------------------------------------------------------------------------- +-- >>> clone 5 'a' +-- "aaaaa" +-- +-- >>> clone 3 "cat" +-- ["cat","cat","cat"] + +clone :: Int -> a -> [a] +clone n x + | n <= 0 = error "fill this in" + | otherwise = error "fill this in" + +------------------------------------------------------------------------------- +-- | Padding a List +------------------------------------------------------------------------------- +data Dir = DirL | DirR + deriving (Eq, Show) + +-- >>> pad DirL 10 0 [1,2,3,4,5] +-- [0,0,0,0,0,1,2,3,4,5] + +-- >>> pad DirR 10 0 [1,2,3,4,5] +-- [1,2,3,4,5,0,0,0,0,0] + +-- >>> pad DirL 3 0 [1,2,3,4,5] +-- [1,2,3,4,5] + +-- >>> pad DirR 3 0 [1,2,3,4,5] +-- [1,2,3,4,5] + +pad :: Dir -> Int -> a -> [a] -> [a] +pad dir n x ys = error "fill this in" + + +------------------------------------------------------------------------------- +-- | 'isSubSequence s1 s2' returns True if `s1` is a sub-sequence of `s2` i.e. +-- if `s1` can be obtained by deleting some elements of `s2` +------------------------------------------------------------------------------- +-- >>> isSubSequence "cat" "dog" +-- False +-- +-- >>> isSubSequence "cat" "craptasticdog" +-- True +-- +isSubSequence :: (Eq a) => [a] -> [a] -> Bool +isSubSequence [] _ = error "fill this in" +isSubSequence _ [] = error "fill this in" +isSubSequence xxs@(x:xs) (y:ys) = error "fill this in" + +------------------------------------------------------------------------------- +-- | maximum +------------------------------------------------------------------------------- +-- >>> maximum 99 [] +-- 99 +-- +-- >>> maximum 99 [90, 100, 200, 52] +-- 200 + +maximum :: (Ord a) => a -> [a] -> a +maximum d xs = foldr f base xs + where + base = error "fill this in" + f = error "fill this in" + +------------------------------------------------------------------------------- +-- | intersperse +------------------------------------------------------------------------------- +-- >>> intersp '|' "chewbacca" +-- "|c|h|e|w|b|a|c|c|a|" + +-- >>> intersp "yo!" ["which", "way", "is", "the", "park"] +-- ["yo!","which","yo!","way","yo!","is","yo!","the","yo!","park","yo!"] + +intersp :: a -> [a] -> [a] +intersp s xs = foldr f base xs + where + base = error "fill this in" + f x r = error "fill this in" + +------------------------------------------------------------------------------- +-- Higher Order: iter +------------------------------------------------------------------------------- +-- >>> iter 10 (\x -> 2 * x) 1 +-- 1024 + +iter :: Int -> (a -> a) -> a -> a +iter 0 _ x = error "fill this in" +iter n f x = error "fill this in" diff --git a/src/CSE230/Shapes.hs b/src/CSE230/Shapes.hs new file mode 100644 index 0000000..844e454 --- /dev/null +++ b/src/CSE230/Shapes.hs @@ -0,0 +1,106 @@ +module CSE230.Shapes where + +import Graphics.Htdp +import CSE230.List +import CSE230.Graphics + +------------------------------------------------------------------------------- +main :: IO () +------------------------------------------------------------------------------- +main = do + mkRainbow + mkChess1 + mkChess2 + mkTriangle1 + mkTriangle2 + mkCarpet + +------------------------------------------------------------------------------- +-- | Rainbow with 'map' +------------------------------------------------------------------------------- +mkRainbow :: IO () +mkRainbow = save "img/rainbow.png" rainbow + +rainbow :: Image +rainbow = foldr1 f xs + where + xs = map g [1..7] + f = error "fill this in" + g = error "fill this in" + + +------------------------------------------------------------------------------- +-- | ChessBoard with 'clone' +------------------------------------------------------------------------------- +mkChess1 :: IO () +mkChess1 = save "img/chess1.png" chessBoard1 + +chessBoard1 :: Image +chessBoard1 = aboves (clone 4 row) + where + row = besides (clone 4 gridSquare) + +gridSquare :: Image +gridSquare = aboves [ besides [ whSq, blSq ] + , besides [ blSq, whSq ] ] + where + whSq = square 50 solid bgCol + blSq = square 50 solid fgCol + +------------------------------------------------------------------------------- +-- | ChessBoard with `iter` +------------------------------------------------------------------------------- +mkChess2 :: IO () +mkChess2 = save "img/chess2.png" chessBoard2 + +chessBoard2 :: Image +chessBoard2 = iter 2 f base + where + f = error "fill this in" + base = gridSquare + + +------------------------------------------------------------------------------- +-- | Sierpinski Triangle with `iter` +------------------------------------------------------------------------------- +mkTriangle1 :: IO () +mkTriangle1 = save "img/triangle1.png" sierpinskiTriangle1 + +sierpinskiTriangle1 :: Image +sierpinskiTriangle1 = triRec 8 + +triRec :: Int -> Image +triRec 0 = blueTriangle +triRec n = error "fill this in" + +blueTriangle :: Image +blueTriangle = triangle 5 solid fgCol + +------------------------------------------------------------------------------- +-- | Sierpinski Triangle with `iter` +------------------------------------------------------------------------------- +mkTriangle2 :: IO () +mkTriangle2 = save "img/triangle2.png" sierpinskiTriangle2 + +sierpinskiTriangle2 :: Image +sierpinskiTriangle2 = iter 8 f base + where + f = error "fill this in" + base = blueTriangle + + +------------------------------------------------------------------------------- +-- | Sierpinski Carpet with `iter` +------------------------------------------------------------------------------- +mkCarpet :: IO () +mkCarpet = save "img/carpet.png" sierpinskiCarpet + +sierpinskiCarpet :: Image +sierpinskiCarpet = iter 4 f base + where + f = error "fill this in" + base = blueSquare + +blueSquare :: Image +blueSquare = square 4 solid fgCol + diff --git a/src/Main.hs b/src/Main.hs new file mode 100644 index 0000000..a1b02f0 --- /dev/null +++ b/src/Main.hs @@ -0,0 +1,12 @@ +module Main where + +import qualified CSE230.Shapes as Shapes +import qualified CSE230.Directory as Directory +import System.Environment (getArgs) + +main :: IO () +main = do + args <- getArgs + case args of + [] -> Shapes.main + _ -> Directory.main args \ No newline at end of file diff --git a/stack.yaml b/stack.yaml new file mode 100644 index 0000000..59726a4 --- /dev/null +++ b/stack.yaml @@ -0,0 +1,8 @@ +resolver: lts-14.12 +allow-newer: true +packages: + - . +extra-deps: +- htdp-image-1.1.0.0 +- AC-Angle-1.0 +- gloss-export-0.1.0.2 diff --git a/test/Common.hs b/test/Common.hs new file mode 100644 index 0000000..2b20073 --- /dev/null +++ b/test/Common.hs @@ -0,0 +1,91 @@ + +{-# LANGUAGE ScopedTypeVariables #-} + +module Common where + +import Data.IORef +import Test.Tasty +import Test.Tasty.HUnit +import System.Exit +import System.Process +import System.IO +import Control.Exception +import Text.Printf +import System.FilePath +import qualified Test.QuickCheck as QC + +type Score = IORef (Int, Int) + +runTests :: [Score -> TestTree] -> IO () +runTests groups = do + sc <- initScore + -- defaultMain (tests sc groups) `catch` (\(e :: ExitCode) -> do + defaultMain (localOption (mkTimeout 2000000) (tests sc groups)) `catch` (\(e :: ExitCode) -> do + (n, tot) <- readIORef sc + putStrLn ("OVERALL SCORE = " ++ show n ++ " / "++ show tot) + throwIO e) + +tests :: Score -> [Score -> TestTree] -> TestTree +tests x gs = testGroup "Tests" [ g x | g <- gs ] + +-------------------------------------------------------------------------------- +-- | Construct a single test case +-------------------------------------------------------------------------------- +mkTest' :: (Show b, Eq b) => Score -> (a -> IO b) -> a -> b -> String -> TestTree +-------------------------------------------------------------------------------- +mkTest' sc f x r name = scoreTest' sc (f, x, r, 1, name) + +-------------------------------------------------------------------------------- +scoreTest' :: (Show b, Eq b) => Score -> (a -> IO b, a, b, Int, String) -> TestTree +-------------------------------------------------------------------------------- +scoreTest' sc (f, x, expR, points, name) = + testCase name $ do + updateTotal sc points + actR <- f x + if actR == expR + then updateCurrent sc points + else assertFailure "Wrong Result" + +updateTotal :: Score -> Int -> IO () +updateTotal sc n = modifyIORef sc (\(x, y) -> (x, y + n)) + +updateCurrent :: Score -> Int -> IO () +updateCurrent sc n = modifyIORef sc (\(x, y) -> (x + n, y)) + +initScore :: IO Score +initScore = newIORef (0, 0) + +-------------------------------------------------------------------------------- +scoreProp :: (QC.Testable prop) => Score -> (String, prop, Int) -> TestTree +-------------------------------------------------------------------------------- +scoreProp sc (name, prop, n) = scoreTest' sc (act, (), True, n, name) + where + act _ = QC.isSuccess <$> QC.labelledExamplesWithResult args prop + args = QC.stdArgs { QC.chatty = False, QC.maxSuccess = 100 } + +-------------------------------------------------------------------------------- +-- | Binary (Executable) Tests +-------------------------------------------------------------------------------- +data BinCmd = BinCmd + { bcCmd :: String + , bcInF :: FilePath + , bcExpF :: FilePath + , bcPoints :: Int + , bcName :: String + } deriving (Show) + +binTest :: Score -> BinCmd -> TestTree +binTest sc b = scoreTest' sc (act, (), True, bcPoints b, bcName b) + where act _ = mkBinTest (bcCmd b) (bcInF b) (bcExpF b) + +mkBinTest execS inF expF = do + hSetBuffering stdout LineBuffering -- or even NoBuffering + withFile log WriteMode $ \h -> do + (_,_,_,ph) <- createProcess $ (shell cmd) {std_out = UseHandle h, std_err = UseHandle h} + c <- waitForProcess ph + expected <- readFile expF + actual <- readFile log + return (expected == actual) + where + log = inF <.> "log" + cmd = printf "%s < %s" execS inF \ No newline at end of file diff --git a/test/Test.hs b/test/Test.hs new file mode 100644 index 0000000..ed91161 --- /dev/null +++ b/test/Test.hs @@ -0,0 +1,115 @@ +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE OverloadedStrings #-} + +import Test.Tasty +import Common +import Prelude hiding (maximum) +-- import System.FilePath (()) +import CSE230.List +import CSE230.Shapes hiding (main) +import CSE230.Doc +-- import CSE230.Graphics +import CSE230.Directory hiding (main) + +main :: IO () +main = runTests + [ probList + , probShapes + , probDoc + -- , probDocProps + , probDir + , probHtree + ] + +probList :: Score -> TestTree +probList sc = testGroup "List" [ + scoreTest ((\_ -> clone 5 'a'), (), "aaaaa", 1, "clone-1"), + scoreTest ((\_ -> clone 3 "cat"), (), ["cat","cat","cat"], 1, "clone-2"), + scoreTest ((\_ -> pad DirL 10 0 [1,2,3,4,5]), (), [0,0,0,0,0,1,2,3,4,5], 1, "pad-1"), + scoreTest ((\_ -> pad DirR 10 0 [1,2,3,4,5]), (), [1,2,3,4,5,0,0,0,0,0], 1, "pad-2"), + scoreTest ((\_ -> pad DirL 3 0 [1,2,3,4,5]), (), [1,2,3,4,5], 1, "pad-3"), + scoreTest ((\_ -> pad DirR 3 0 [1,2,3,4,5]), (), [1,2,3,4,5], 1, "pad-4"), + scoreTest ((\_ -> isSubSequence "cat" "dog"), (), False, 1, "issub-1"), + scoreTest ((\_ -> isSubSequence "cat" "craptasticdog"), (), True, 1, "issub-2"), + scoreTest ((\_ -> maximum 99 []), (), 99, 1, "maximum-1"), + scoreTest ((\_ -> maximum 99 [90, 100, 200, 52]), (), 200, 1, "maximum-2"), + scoreTest ((\_ -> intersp '|' "chewbacca"), (), "|c|h|e|w|b|a|c|c|a|", 1, "intersp-1"), + scoreTest ((\_ -> intersp "yo!" ["which", "way", "is", "the", "park"]), (), ["yo!","which","yo!","way","yo!","is","yo!","the","yo!","park","yo!"], 1, "maximum-1"), + scoreTest ((\_ -> iter 10 (\x -> 2 * x) 1 ), (), 1024, 3, "iter-1") + ] + where + scoreTest :: (Show b, Eq b) => (a -> b, a, b, Int, String) -> TestTree + scoreTest (f, x, r, n, msg) = scoreTest' sc (return . f, x, r, n, msg) + +probShapes :: Score -> TestTree +probShapes sc = testGroup "Shapes" [ + scoreTest ((\_ -> checkImage "rainbow.png" mkRainbow), (), True, 1, "rainbow"), + scoreTest ((\_ -> checkImage "chess1.png" mkChess1), (), True, 2, "chess-1"), + scoreTest ((\_ -> checkImage "chess2.png" mkChess2), (), True, 2, "chess-2"), + scoreTest ((\_ -> checkImage "triangle1.png" mkTriangle1), (), True, 3, "triangle-1"), + scoreTest ((\_ -> checkImage "triangle2.png" mkTriangle2), (), True, 3, "triangle-2"), + scoreTest ((\_ -> checkImage "carpet.png" mkCarpet), (), True, 5, "carpet") + ] + where + scoreTest :: (Show b, Eq b) => (a -> IO b, a, b, Int, String) -> TestTree + scoreTest (f, x, r, n, msg) = scoreTest' sc (f, x, r, n, msg) + + checkImage :: FilePath -> IO () -> IO Bool + checkImage f build = build >> return True -- compareBMP 5 ("img" f) ("out" f) + + +probDoc :: Score -> TestTree +probDoc sc = testGroup "Doc" [ + scoreTest ((\_ -> lshow (foldr vcatL empty animals)), (), ["cat","horse","mongoose"], 1, "vcatL"), + scoreTest ((\_ -> lshow (foldr vcatR empty animals)), (), [" cat"," horse","mongoose"], 2, "vcatR"), + scoreTest ((\_ -> lshow $ hcatT aDoc lineDoc), (), ["a <----- HERE","aaa ","aaaaa"], 2, "hcatT-1"), + scoreTest ((\_ -> lshow $ hcatT aDoc bDoc), (), ["a b","aaa bbb","aaaaa"], 2, "hcatT-2"), + scoreTest ((\_ -> lshow $ hcatB aDoc lineDoc), (), ["a ","aaa ","aaaaa<----- HERE"], 2, "hcatB-1"), + scoreTest ((\_ -> lshow $ hcatB aDoc bDoc), (), ["a ","aaa b","aaaaabbb"], 2, "hcatB-2"), + scoreTest ((\_ -> lshow $ foldr vcatL empty triangles), (), ["*","***","*****","* *","*** ***","**********","* * *","*** *** ***","***************"], 3, "vcatL-1"), + scoreTest ((\_ -> lshow $ foldr vcatR empty triangles), (), [" *"," ***"," *****"," * *"," *** ***"," **********","* * *","*** *** ***","***************"], 3, "vcatR-1") + ] + where + scoreTest :: (Show b, Eq b) => (a -> b, a, b, Int, String) -> TestTree + scoreTest (f, x, r, n, msg) = scoreTest' sc (return . f, x, r, n, msg) + +probDocProps :: Score -> TestTree +probDocProps sc = testGroup "Doc-Properties" + [ scoreProp sc ("prop_hcatT_width", prop_hcatT_width , 2) + , scoreProp sc ("prop_hcatT_height", prop_hcatT_height, 2) + , scoreProp sc ("prop_hcatB_width", prop_hcatB_width , 2) + , scoreProp sc ("prop_hcatB_height", prop_hcatB_height, 2) + , scoreProp sc ("prop_vcatL_width", prop_vcatL_width , 2) + , scoreProp sc ("prop_vcatR_width", prop_vcatR_width , 2) + , scoreProp sc ("prop_vcatL_height", prop_vcatL_height, 2) + , scoreProp sc ("prop_vcatR_height", prop_vcatR_height, 2) + ] + +probDir :: Score -> TestTree +probDir sc = testGroup "Directory" [ + scoreTest ((\_ -> lshow (dirDoc example)), (), ddEx, 4, "dirDoc-1"), + scoreTest ((\_ -> lshow (dirDoc srcDir)), (), ddSrc, 4, "dirDoc-2"), + scoreTest ((\_ -> allFiles example), (), fiEx, 2, "allFiles-1"), + scoreTest ((\_ -> allDirs example), (), diEx, 2, "allDirs-1"), + scoreTest ((\_ -> findFiles ".hs" example), (), hsEx, 4, "findFiles-1"), + scoreTestI ((\_ -> build "src"), (), srcDir, 1, "maximum-1") + ] + where + scoreTest :: (Show b, Eq b) => (a -> b, a, b, Int, String) -> TestTree + scoreTest (f, x, r, n, msg) = scoreTest' sc (return . f, x, r, n, msg) + scoreTestI :: (Show b, Eq b) => (a -> IO b, a, b, Int, String) -> TestTree + scoreTestI (f, x, r, n, msg) = scoreTest' sc (f, x, r, n, msg) + ddEx = [".","\9500\9472\9472 LICENSE","\9500\9472\9472 README.md","\9500\9472\9472 cse230-tree.cabal","\9500\9472\9472 out","\9474 \9500\9472\9472 carpet.png","\9474 \9500\9472\9472 chess1.png","\9474 \9500\9472\9472 chess2.png","\9474 \9500\9472\9472 rainbow.png","\9474 \9500\9472\9472 triangle1.png","\9474 \9492\9472\9472 triangle2.png","\9500\9472\9472 src","\9474 \9500\9472\9472 Main.hs","\9474 \9492\9472\9472 CSE230","\9474 \9500\9472\9472 Directory.hs","\9474 \9500\9472\9472 Doc.hs","\9474 \9500\9472\9472 Graphics.hs","\9474 \9500\9472\9472 List.hs","\9474 \9492\9472\9472 Shapes.hs","\9492\9472\9472 stack.yaml"] + -- ddEx = [".","\9500\9472\9472 LICENSE","\9500\9472\9472 README.md","\9500\9472\9472 cse230-tree.cabal","\9500\9472\9472 out","\9474 \9500\9472\9472 carpet.png","\9474 \9500\9472\9472 chess1.png","\9474 \9500\9472\9472 chess2.png","\9474 \9500\9472\9472 rainbow.png","\9474 \9500\9472\9472 triangle1.png","\9474 \9492\9472\9472 triangle2.png","\9500\9472\9472 src","\9474 \9500\9472\9472 Main.hs","\9474 \9492\9472\9472 CSE230","\9474 \9500\9472\9472 Directory.hs","\9474 \9500\9472\9472 Doc.hs","\9474 \9500\9472\9472 Graphics.hs","\9474 \9500\9472\9472 List.hs","\9474 \9492\9472\9472 Shapes.hs","\9492\9472\9472 stack.yaml"] + ddSrc = ["src","\9500\9472\9472 CSE230","\9474 \9500\9472\9472 Directory.hs","\9474 \9500\9472\9472 Doc.hs","\9474 \9500\9472\9472 Graphics.hs","\9474 \9500\9472\9472 List.hs","\9474 \9492\9472\9472 Shapes.hs","\9492\9472\9472 Main.hs"] + fiEx = ["LICENSE","README.md","cse230-tree.cabal","carpet.png","chess1.png","chess2.png","rainbow.png","triangle1.png","triangle2.png","Main.hs","Directory.hs","Doc.hs","Graphics.hs","List.hs","Shapes.hs","stack.yaml"] + diEx = [".","out","src","CSE230"] + hsEx = ["./src/Main.hs","./src/CSE230/Directory.hs","./src/CSE230/Doc.hs","./src/CSE230/Graphics.hs","./src/CSE230/List.hs","./src/CSE230/Shapes.hs"] + +probHtree :: Score -> TestTree +probHtree sc = testGroup "htree" + [ bc "stack exec -- htree -ls src" "out/htree-ls.txt" 4 "htree-ls" + , bc "stack exec -- htree -find src .hs" "out/htree-find.txt" 4 "htree-find" + ] + where + bc cmd f pts name = binTest sc (BinCmd cmd f f pts name)