]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.scl.tutorial/scl/Tutorial/2.03 (Exercise) Pipelines.md
Merge "Fix to OrderedSetTemplate, preference for autocompletion"
[simantics/platform.git] / bundles / org.simantics.scl.tutorial / scl / Tutorial / 2.03 (Exercise) Pipelines.md
1 [pipes.txt]: "http://www.simantics.org/~niemisto/SCL20150513/pipes.txt"\r
2 \r
3 ## Pipelines exercise\r
4 \r
5 In this exercise, you create a model configuration based on the data in the file [pipes.txt].\r
6 \r
7 ### Step 1\r
8 \r
9 Implement a function\r
10 \r
11     isNonemptyString :: String -> Boolean\r
12 \r
13 that returns true, when the string given as a parameter is nonempty:\r
14 \r
15     > isNonemptyString ""\r
16     False\r
17     > isNonemptyString "foo"\r
18     True\r
19 \r
20 You need the empty string `""` and the inequality comparison:\r
21     \r
22 ::value[Prelude/!=]\r
23 \r
24 ### Step 2\r
25 \r
26 Implement a function\r
27 \r
28     removeComment :: String -> String\r
29 \r
30 that removes a comment from a line. A comment starts with `!` and continues\r
31 to the end of the line. It should also remove leading and trailing whitespace. For example\r
32     \r
33     > removeComment "A;B;C ! This is a comment"\r
34     "A;B;C"\r
35     > removeComment "Hello World!"\r
36     "Hello World"\r
37     > removeComment "This line contains no comments."\r
38     "This line contains no comments."\r
39 \r
40 The following functions are useful here:\r
41 \r
42 ::value[Prelude/splitString, Prelude/!, Prelude/trim]\r
43 \r
44 ### Step 3\r
45 \r
46 New, lets read the file [pipes.txt]. Store it to somewhere in your file system.\r
47 \r
48 You need to import the module `StringIO`, either using the import dialog or\r
49 with the command\r
50 \r
51     import "StringIO"\r
52 \r
53 Now, try to read the file using\r
54 \r
55 ::value[StringIO/readLines]\r
56 \r
57 Remember that `\` is an escape character in SCL. A string containing directory separators\r
58 must be written in one of the following forms:\r
59 \r
60     "c:/temp/pipes.txt"\r
61     "c:\\temp\\pipes.txt"\r
62 \r
63 ### Step 4\r
64 \r
65 As you see, the file contains empty lines and comments. Implement a function\r
66 \r
67     loadAndPreprocess :: String -> <Proc> [String]\r
68     \r
69 that reads the file, whose name is given as a parameter, removes the comments, empty lines and leading and trailing\r
70 whitespace at every line. It returns the preprocessed lines. You need the\r
71 functions `isNonemptyString`, `removeComment` you implemented before,\r
72 `readLines` and the following functions\r
73 \r
74 ::value[Prelude/map,Prelude/filter]\r
75 \r
76 ### Step 5\r
77 \r
78 Create a new SCL module and move your definitions there (if you have not done so already). It\r
79 is much easier to continue handling the increasing number of function definitions there.\r
80 \r
81 ### Step 6\r
82 \r
83 You may have noticed that the lines in the preprocessed file have entries separated by `;`.\r
84 The first entry in each line is either "POINT" or "PIPE". Implement the functions \r
85 \r
86     isPointLine, isPipeLine :: [String] -> Boolean\r
87 \r
88 that check whether the first string in a list of strings is "POINT" or "PIPE".\r
89 For example\r
90 \r
91     > isPointLine ["POINT", "1", "2", "3.4", "100", "200"]\r
92 \r
93 ### Step 7\r
94 \r
95 Now, add the following definitions to your SCL module:\r
96 \r
97 ~~~\r
98 handlePointEntry :: String -> [String] -> <Proc> ()\r
99 handlePointEntry diagram ["POINT", id, pointElevation, x, y] = do\r
100     print "Add a point \(id) into the diagram \(diagram) with elevation \(pointElevation) at coordinates \(x),\(y)."\r
101     \r
102 handlePipeEntry :: String -> Integer -> [String] -> <Proc> ()\r
103 handlePipeEntry diagram id ["PIPE", id1, id2, pipeLength] = do\r
104     print "Add a pipe \(id) into the diagram \(diagram) connecting the point \(id1) to the point \(id2) with length \(pipeLength)"\r
105 ~~~\r
106 \r
107 Create a function\r
108 \r
109     readPipesFile :: String -> String -> <Proc> ()\r
110     \r
111 that is called as\r
112 \r
113     readPipesFile "diagramName" "fileName"\r
114     \r
115 It should first read the file and preprocess it using `loadAndPreprocess` you implemented in Step 4.\r
116 It should then split each line into entries with `splitString` and `map`\r
117 Note that because of the order of the parameters of `splitString` you need either anonymous\r
118 functions, a separate funtion definition or\r
119 ::value[Prelude/flip]\r
120 \r
121 It should then filter the lines into two lists, one containing all definitions of points\r
122 and one all definitions of pipes. Finally, the function should call \r
123 `handlePointEntry` for all points using\r
124 \r
125 ::value[Prelude/iter]\r
126 \r
127 and `handlePipeEntry` for all pipes using (because `handlePipeEntry` has an extra integer parameter)\r
128 \r
129 ::value[Prelude/iterI] \r
130 \r
131 When finished the function should work like this from the console:\r
132 \r
133 ~~~\r
134 > readPipesFile "X" "c:/temp/pipes.txt"\r
135 Add a point 1 into the diagram X with elevation 0 at coordinates 100,100.\r
136 Add a point 2 into the diagram X with elevation 1.1 at coordinates 120,100.\r
137 Add a point 3 into the diagram X with elevation 1.5 at coordinates 140,100.\r
138 Add a point 4 into the diagram X with elevation 2.5 at coordinates 160,100.\r
139 Add a pipe 0 into the diagram X connecting the point 1 to the point 2 with length 12\r
140 Add a pipe 1 into the diagram X connecting the point 2 to the point 3 with length 11\r
141 Add a pipe 2 into the diagram X connecting the point 3 to the point 4 with length 13\r
142 ~~~\r
143 \r
144 ### Step 8\r
145 \r
146 Reimplement the function `handlePointEntry` so that it creates the points into the diagram with the\r
147 specified elevation and diagram coordinates. You need the functions\r
148 \r
149 ::value[Apros/Legacy/aadd, Apros/Legacy/amodi]\r
150 \r
151 You need also the function\r
152 \r
153 ::value[Prelude/read]\r
154 \r
155 to convert the diagram coordinates from strings to doubles.\r
156 Add some prefix to the point indicies to form the point name (for example\r
157 `name = "PO" + id`. The name of the elevation attribute is\r
158 `PO11_ELEV`.\r
159 \r
160 Test your implementation with the example data.\r
161 \r
162 ### Step 9\r
163 \r
164 Reimplement the function `handlePipeEntry` so that it creates the pipes into the diagram with\r
165 the specified length (`PI12_LENGTH`) and connects it to the specified points\r
166 (`PI12_CONNECT_POINT_1` and `PI12_CONNECT_POINT_2`). You may place the pipes to the origin\r
167 `(0,0)`.\r
168 \r
169 Test your implementation again with the example data.\r
170 \r
171 ### Step 10\r
172 \r
173 In this final step, fix the coordinates of the pipes so that they are located between the points\r
174 they connect.\r
175 \r
176 You may do this in the following way. In `readPipesFile`, define a function\r
177     \r
178     coordinates :: String -> Maybe (Double, Double)\r
179     \r
180 that gives the coordinates of a point when given the index of the point. You may create it\r
181 by partially applying the function\r
182 \r
183 ::value[Prelude/index]\r
184 \r
185 for this. Then, add the new parameter `coordinates` to the function `handlePipeEntry` so that\r
186 its signature becomes\r
187 \r
188     String -> (String -> Maybe (Double, Double)) -> Integer -> [String] -> <Proc> ()\r
189     \r
190 Now, you may read the coordinates of the points in `handlePipeEntry` like this\r
191 \r
192     (x1,y1) = fromJust (coordinates id1)\r
193     \r
194 where\r
195 \r
196 ::value[Prelude/fromJust]\r
197 \r
198 Use the average of the connected points as the diagram coordinate for the pipe.\r
199