Pipelines exercise
In this exercise, you create a model configuration based on the data in the file pipes.txt. 
Step 1
Implement a function 
isNonemptyString :: String -> Boolean
 
that returns true, when the string given as a parameter is nonempty: 
> isNonemptyString ""
False
> isNonemptyString "foo"
True
 
You need the empty string "" and the inequality comparison: 
(!=) :: a -> a -> Boolean (Prelude) 
  
Step 2
Implement a function 
removeComment :: String -> String
 
that removes a comment from a line. A comment starts with ! and continues
to the end of the line. It should also remove leading and trailing whitespace. For example 
> removeComment "A;B;C ! This is a comment"
"A;B;C"
> removeComment "Hello World!"
"Hello World"
> removeComment "This line contains no comments."
"This line contains no comments."
 
The following functions are useful here: 
splitString :: String -> String -> [String] (Prelude) 
splitString text pattern splits the string into a list of string where the parts are sepratated in the original list by the given pattern. 
  
(!) :: IndexedSequence a => a b -> Integer -> b (Prelude) 
seq ! i returns the ith element of the sequence seq. Indexing starts from zero. 
  
trim :: String -> String (Prelude) 
Removes leading and trailing whitespace from the string. 
  
Step 3
New, lets read the file pipes.txt. Store it to somewhere in your file system. 
You need to import the module StringIO, either using the import dialog or
with the command 
import "StringIO"
 
Now, try to read the file using 
readLines :: String -> <Proc> [String] (StringIO) 
readLines "filename" reads all lines of the file specified by fileName.
The file contents are expected to be UTF-8 encoded. 
  
Remember that \ is an escape character in SCL. A string containing directory separators
must be written in one of the following forms: 
"c:/temp/pipes.txt"
"c:\\temp\\pipes.txt"
 
Step 4
As you see, the file contains empty lines and comments. Implement a function 
loadAndPreprocess :: String -> <Proc> [String]
 
that reads the file, whose name is given as a parameter, removes the comments, empty lines and leading and trailing
whitespace at every line. It returns the preprocessed lines. You need the
functions isNonemptyString, removeComment you implemented before,
readLines and the following functions 
map :: FunctorE a => (b -> <d> c) -> a b -> <d> a c (Prelude) 
Applies the function to all elements of the container and
returns the similarly shaped container with the results: 
For lists, 
map f [e1, e2, ..., eN] = [f e1, f e2, ..., f eN]
 
for example 
map (*2) [1..5] = [2, 4, 6, 8, 10]
 
  
filter :: MonadZeroE a => (b -> <c> Boolean) -> a b -> <c> a b (Prelude) 
  
Step 5
Create a new SCL module and move your definitions there (if you have not done so already). It
is much easier to continue handling the increasing number of function definitions there. 
Step 6
You may have noticed that the lines in the preprocessed file have entries separated by ;.
The first entry in each line is either "POINT" or "PIPE". Implement the functions 
isPointLine, isPipeLine :: [String] -> Boolean
 
that check whether the first string in a list of strings is "POINT" or "PIPE".
For example 
> isPointLine ["POINT", "1", "2", "3.4", "100", "200"]
 
Step 7
Now, add the following definitions to your SCL module: 
handlePointEntry :: String -> [String] -> <Proc> ()
handlePointEntry diagram ["POINT", id, pointElevation, x, y] = do
    print "Add a point \(id) into the diagram \(diagram) with elevation \(pointElevation) at coordinates \(x),\(y)."
    
handlePipeEntry :: String -> Integer -> [String] -> <Proc> ()
handlePipeEntry diagram id ["PIPE", id1, id2, pipeLength] = do
    print "Add a pipe \(id) into the diagram \(diagram) connecting the point \(id1) to the point \(id2) with length \(pipeLength)"
 
Create a function 
readPipesFile :: String -> String -> <Proc> ()
 
that is called as 
readPipesFile "diagramName" "fileName"
 
It should first read the file and preprocess it using loadAndPreprocess you implemented in Step 4.
It should then split each line into entries with splitString and map
Note that because of the order of the parameters of splitString you need either anonymous
functions, a separate funtion definition or 
flip :: (a -> b -> <d> c) -> b -> a -> <d> c (Prelude) 
Flips the parameters of a binary function. 
  
It should then filter the lines into two lists, one containing all definitions of points
and one all definitions of pipes. Finally, the function should call
handlePointEntry for all points using 
iter :: FunctorE a => (b -> <d> c) -> a b -> <d> () (Prelude) 
Calls the given function with all elements of the given container. 
  
and handlePipeEntry for all pipes using (because handlePipeEntry has an extra integer parameter) 
iterI :: FunctorE a => (Integer -> b -> <d> c) -> a b -> <d> () (Prelude) 
Calls the given function with all elements of the given container giving also the index of the element as a parameter. 
  
When finished the function should work like this from the console: 
> readPipesFile "X" "c:/temp/pipes.txt"
Add a point 1 into the diagram X with elevation 0 at coordinates 100,100.
Add a point 2 into the diagram X with elevation 1.1 at coordinates 120,100.
Add a point 3 into the diagram X with elevation 1.5 at coordinates 140,100.
Add a point 4 into the diagram X with elevation 2.5 at coordinates 160,100.
Add a pipe 0 into the diagram X connecting the point 1 to the point 2 with length 12
Add a pipe 1 into the diagram X connecting the point 2 to the point 3 with length 11
Add a pipe 2 into the diagram X connecting the point 3 to the point 4 with length 13
 
Step 8
Reimplement the function handlePointEntry so that it creates the points into the diagram with the
specified elevation and diagram coordinates. You need the functions 
You need also the function 
read :: Read a => String -> a (Prelude) 
Converts a string to a required type of value. 
  
to convert the diagram coordinates from strings to doubles.
Add some prefix to the point indicies to form the point name (for example
name = "PO" + id. The name of the elevation attribute is
PO11_ELEV. 
Test your implementation with the example data. 
Step 9
Reimplement the function handlePipeEntry so that it creates the pipes into the diagram with
the specified length (PI12_LENGTH) and connects it to the specified points
(PI12_CONNECT_POINT_1 and PI12_CONNECT_POINT_2). You may place the pipes to the origin
(0,0). 
Test your implementation again with the example data. 
Step 10
In this final step, fix the coordinates of the pipes so that they are located between the points
they connect. 
You may do this in the following way. In readPipesFile, define a function 
coordinates :: String -> Maybe (Double, Double)
 
that gives the coordinates of a point when given the index of the point. You may create it
by partially applying the function 
index :: [(a, b)] -> a -> Maybe b (Prelude) 
Given a list of key-value pairs, the function produces a function that finds a value
efficiently for the given key. 
  
for this. Then, add the new parameter coordinates to the function handlePipeEntry so that
its signature becomes 
String -> (String -> Maybe (Double, Double)) -> Integer -> [String] -> <Proc> ()
 
Now, you may read the coordinates of the points in handlePipeEntry like this 
(x1,y1) = fromJust (coordinates id1)
 
where 
fromJust :: Maybe a -> a (Prelude) 
Given Just x this function returns x. If the parameter is Nothing, the function raises an exception. 
  
Use the average of the connected points as the diagram coordinate for the pipe. 
 |