[pipes.txt]: "http://www.simantics.org/~niemisto/SCL20150513/pipes.txt" ## 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: ::value[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: ::value[Prelude/splitString, Prelude/!, Prelude/trim] ### 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 ::value[StringIO/readLines] 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 -> [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 ::value[Prelude/map,Prelude/filter] ### 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] -> () 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] -> () 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 -> () 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 ::value[Prelude/flip] 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 ::value[Prelude/iter] and `handlePipeEntry` for all pipes using (because `handlePipeEntry` has an extra integer parameter) ::value[Prelude/iterI] 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 ::value[Apros/Legacy/aadd, Apros/Legacy/amodi] You need also the function ::value[Prelude/read] 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 ::value[Prelude/index] for this. Then, add the new parameter `coordinates` to the function `handlePipeEntry` so that its signature becomes String -> (String -> Maybe (Double, Double)) -> Integer -> [String] -> () Now, you may read the coordinates of the points in `handlePipeEntry` like this (x1,y1) = fromJust (coordinates id1) where ::value[Prelude/fromJust] Use the average of the connected points as the diagram coordinate for the pipe.