Ticket #3638: KeyWordIO.mo

File KeyWordIO.mo, 15.7 KB (added by Christian Kral <dr.christian.kral@…>, 9 years ago)

Library to read parameters from external file

Line 
1within ;
2package KeyWordIO "Read and write data files with key words"
3 function writeRealVariable "Writing real variable to file"
4 input String fileName "Name of file" annotation(Dialog(__Dymola_loadSelector(filter = "Text files (*.txt; *.dat)", caption = "Open file in which Real parameters are present")));
5 input String name "Name of parameter";
6 input Real data "Actual value of parameter";
7 input Boolean append = false "Append data to file";
8 algorithm
9 if not append then
10 Modelica.Utilities.Files.removeFile(fileName);
11 end if;
12 Modelica.Utilities.Streams.print(name + " = " + String(data), fileName);
13 end writeRealVariable;
14
15 function writeRealVariables "Write multiple real variables to file"
16 input String fileName "Name of file" annotation(Dialog(__Dymola_loadSelector(filter = "Text files (*.txt; *.dat)", caption = "Open file in which Real parameters are present")));
17 input String name[:] "Name of parameter";
18 input Real data[:] "Actual value of parameter";
19 input Boolean append = false "Append data to file";
20 algorithm
21 // Check sizes of name and data
22 if size(name, 1) <> size(data, 1) then
23 assert(false, "writeReadParameters: Lengths of name and data have to be equal");
24 end if;
25 // Write data to file
26 if not append then
27 Modelica.Utilities.Files.removeFile(fileName);
28 end if;
29 for k in 1:size(name, 1) loop
30 Modelica.Utilities.Streams.print(name[k] + " = " + String(data[k]), fileName);
31 end for;
32 end writeRealVariables;
33
34 function readRealParameter "Read the value of a Real parameter from file"
35 import Modelica.Utilities.*;
36 input String fileName "Name of file" annotation(Dialog(__Dymola_loadSelector(filter = "Text files (*.txt; *.dat)", caption = "Open file in which Real parameters are present")));
37 input String name "Name of parameter";
38 input Boolean cache = false "Read file with/without caching";
39 output Real result "Actual value of parameter on file";
40 protected
41 String line;
42 String identifier;
43 String delimiter;
44 Integer nextIndex;
45 Integer iline = 1;
46 Types.TokenValue token;
47 String message = "in file \"" + fileName + "\" on line ";
48 String message2;
49 Boolean found = false;
50 Boolean endOfFile = false;
51 algorithm
52 if not cache then
53 (line, endOfFile) := readLineWithoutCache(fileName, iline);
54 else
55 (line, endOfFile) := Streams.readLine(fileName, iline);
56 end if;
57 while not found and not endOfFile loop
58 (token, nextIndex) := Strings.scanToken(line);
59 if token.tokenType == Types.TokenType.NoToken then
60 iline := iline + 1;
61 elseif token.tokenType == Types.TokenType.IdentifierToken then
62 if token.string == name then
63 message2 := message + String(iline);
64 (delimiter, nextIndex) := Strings.scanDelimiter(line, nextIndex, {"="}, message2);
65 (result, nextIndex) := expression(line, nextIndex, message2);
66 (delimiter, nextIndex) := Strings.scanDelimiter(line, nextIndex, {";", ""}, message2);
67 Strings.scanNoToken(line, nextIndex, message2);
68 found := true;
69 else
70 iline := iline + 1;
71 end if;
72 else
73 Strings.syntaxError(line, nextIndex, "Expected identifier " + message + String(iline));
74 end if;
75 if not cache then
76 (line, endOfFile) := readLineWithoutCache(fileName, iline);
77 else
78 (line, endOfFile) := Streams.readLine(fileName, iline);
79 end if;
80 end while;
81 // skip line
82 // name found, get value of "name = value;"
83 // wrong name, skip line
84 // wrong token
85 // read next line
86 /*
87 if not cache then
88 Streams.close(fileName);
89 end if;
90 */
91 if not found then
92 Streams.error("Parameter \"" + name + "\" not found in file \"" + fileName + "\"");
93 end if;
94 annotation(Documentation(info = "<html>
95<h4>Syntax</h4>
96<blockquote><pre>
97result = <b>readRealParameter</b>(fileName, name);
98</pre></blockquote>
99<h4>Description</h4>
100<p>
101This function demonstrates how a function can be implemented
102that reads the value of a parameter from file. The function
103performs the following actions:
104</p>
105<ol>
106<li> It opens file \"fileName\" and reads the lines of the file.</li>
107<li> In every line, Modelica line comments (\"// ... end-of-line\")
108 are skipped </li>
109<li> If a line consists of \"name = expression\" and the \"name\"
110 in this line is identical to the second argument \"name\"
111 of the function call, the expression calculator Examples.expression
112 is used to evaluate the expression after the \"=\" character.
113 The expression can optionally be terminated with a \";\".</li>
114<li> The result of the expression evaluation is returned as
115 the value of the parameter \"name\". </li>
116</ol>
117<h4>Example</h4>
118<p>
119On file \"test.txt\" the following lines might be present:
120</p>
121<blockquote><pre>
122// Motor data
123J = 2.3 // inertia
124w_rel0 = 1.5*2; // relative angular velocity
125phi_rel0 = pi/3
126</pre></blockquote>
127<p>
128The function returns the value \"3.0\" when called as:
129</p>
130<blockquote><pre>
131readRealParameter(\"test.txt\", \"w_rel0\")
132</pre></blockquote>
133</html>"));
134 end readRealParameter;
135
136 function expression "Expression interpreter that returns with the position after the expression (expression may consist of +,-,*,/,^,(),sin(), cos(), tan(), sqrt(), asin(), acos(), atan(), exp(), log(), pi"
137 import Modelica.Utilities.Types;
138 import Modelica.Utilities.Strings;
139 import Modelica.Math;
140 import Modelica.Constants;
141 input String string "Expression that is evaluated";
142 input Integer startIndex = 1 "Start scanning of expression at character startIndex";
143 input String message = "" "Message used in error message if scan is not successful";
144 output Real result "Value of expression";
145 output Integer nextIndex "Index after the scanned expression";
146 protected
147 function term "Evaluate term of an expression"
148 extends Modelica.Icons.Function;
149 input String string;
150 input Integer startIndex;
151 input String message = "";
152 output Real result;
153 output Integer nextIndex;
154 protected
155 Real result2;
156 Boolean scanning = true;
157 String opString;
158 algorithm
159 // scan for "primary * primary" or "primary / primary"
160 (result, nextIndex) := primary(string, startIndex, message);
161 while scanning loop
162 (opString, nextIndex) := Strings.scanDelimiter(string, nextIndex, {"*", "/", "^", ""}, message);
163 if opString == "" then
164 scanning := false;
165 else
166 (result2, nextIndex) := primary(string, nextIndex, message);
167 result := if opString == "*" then result * result2 else if opString == "/" then result / result2 else result ^ result2;
168 end if;
169 end while;
170 end term;
171
172 function primary "Evaluate primary of an expression"
173 extends Modelica.Icons.Function;
174 input String string;
175 input Integer startIndex;
176 input String message = "";
177 output Real result;
178 output Integer nextIndex;
179 protected
180 Types.TokenValue token;
181 Real result2;
182 String delimiter;
183 String functionName;
184 Real pi = Constants.pi;
185 algorithm
186 (token, nextIndex) := Strings.scanToken(string, startIndex, unsigned= true);
187 if token.tokenType == Types.TokenType.DelimiterToken and token.string == "(" then
188 (result, nextIndex) := expression(string, nextIndex, message);
189 (delimiter, nextIndex) := Strings.scanDelimiter(string, nextIndex, {")"}, message);
190 elseif token.tokenType == Types.TokenType.RealToken then
191 result := token.real;
192 elseif token.tokenType == Types.TokenType.IntegerToken then
193 result := token.integer;
194 elseif token.tokenType == Types.TokenType.IdentifierToken then
195 if token.string == "pi" then
196 result := pi;
197 else
198 functionName := token.string;
199 (delimiter, nextIndex) := Strings.scanDelimiter(string, nextIndex, {"("}, message);
200 (result, nextIndex) := expression(string, nextIndex, message);
201 (delimiter, nextIndex) := Strings.scanDelimiter(string, nextIndex, {")"}, message);
202 if functionName == "sin" then
203 result := Math.sin(result);
204 elseif functionName == "cos" then
205 result := Math.cos(result);
206 elseif functionName == "tan" then
207 result := Math.tan(result);
208 elseif functionName == "sqrt" then
209 if result < 0.0 then
210 Strings.syntaxError(string, startIndex, "Argument of call \"sqrt(" + String(result) + ")\" is negative.\n" + "Imaginary numbers are not supported by the calculator.\n" + message);
211 end if;
212 result := sqrt(result);
213 elseif functionName == "asin" then
214 result := Math.asin(result);
215 elseif functionName == "acos" then
216 result := Math.acos(result);
217 elseif functionName == "atan" then
218 result := Math.atan(result);
219 elseif functionName == "exp" then
220 result := Math.exp(result);
221 elseif functionName == "log" then
222 if result <= 0.0 then
223 Strings.syntaxError(string, startIndex, "Argument of call \"log(" + String(result) + ")\" is negative.\n" + message);
224 end if;
225 result := log(result);
226 else
227 Strings.syntaxError(string, startIndex, "Function \"" + functionName + "\" is unknown (not supported)\n" + message);
228 end if;
229 end if;
230 else
231 Strings.syntaxError(string, startIndex, "Invalid primary of expression.\n" + message);
232 end if;
233 end primary;
234
235 Real result2;
236 String signOfNumber;
237 Boolean scanning = true;
238 String opString;
239 algorithm
240 // scan for optional leading "+" or "-" sign
241 (signOfNumber, nextIndex) := Strings.scanDelimiter(string, startIndex, {"+", "-", ""}, message);
242 // scan for "term + term" or "term - term"
243 (result, nextIndex) := term(string, nextIndex, message);
244 if signOfNumber == "-" then
245 result := -result;
246 end if;
247 while scanning loop
248 (opString, nextIndex) := Strings.scanDelimiter(string, nextIndex, {"+", "-", ""}, message);
249 if opString == "" then
250 scanning := false;
251 else
252 (result2, nextIndex) := term(string, nextIndex, message);
253 result := if opString == "+" then result + result2 else result - result2;
254 end if;
255 end while;
256 annotation(Documentation(info = "<html>
257<h4>Syntax</h4>
258<blockquote><pre>
259 result = <b>expression</b>(string);
260(result, nextIndex) = <b>expression</b>(string, startIndex=1, message=\"\");
261</pre></blockquote>
262<h4>Description</h4>
263<p>
264This function is nearly the same as Examples.<b>calculator</b>.
265The essential difference is that function \"expression\" might be
266used in other parsing operations: After the expression is
267parsed and evaluated, the function returns with the value
268of the expression as well as the position of the character
269directly after the expression.
270</p>
271<p>
272This function demonstrates how a simple expression calculator
273can be implemented in form of a recursive decent parser
274using basically the Strings.scanToken(..) and scanDelimiters(..)
275function. There are 2 local functions (term, primary) that
276implement the corresponding part of the grammar.
277</p>
278<p>
279The following operations are supported (pi=3.14.. is a predefined constant):
280</p>
281<pre>
282 +, -
283 *, /, ^
284 (expression)
285 sin(expression)
286 cos(expression)
287 tan(expression)
288 sqrt(expression)
289 asin(expression)
290 acos(expression)
291 atan(expression)
292 exp(expression)
293 log(expression)
294 pi
295</pre>
296<p>
297The optional argument \"startIndex\" defines at which position
298scanning of the expression starts.
299</p>
300<p>
301In case of error,
302the optional argument \"message\" is appended to the error
303message, in order to give additional information where
304the error occured.
305</p>
306<p>
307This function parses the following grammaer
308</p>
309<pre>
310 expression: [ add_op ] term { add_op term }
311 add_op : \"+\" | \"-\"
312 term : primary { mul_op primary }
313 mul_op : \"*\" | \"/\" | \"^\"
314 primary : UNSIGNED_NUMBER
315 | pi
316 | ( expression )
317 | functionName( expression )
318 function : sin
319 | cos
320 | tan
321 | sqrt
322 | asin
323 | acos
324 | atan
325 | exp
326 | log
327</pre>
328<p>
329Note, in Examples.readRealParameter it is shown, how the expression
330function can be used as part of another scan operation.
331</p>
332<h4>Example</h4>
333<blockquote><pre>
334 expression(\"2+3*(4-1)\"); // returns 11
335 expression(\"sin(pi/6)\"); // returns 0.5
336</pre></blockquote>
337</html>"));
338 end expression;
339
340 function readLineWithoutCache "Reads a line of text from a file without cahing and returns it in a string"
341 input String fileName "Name of the file that shall be read" annotation(Dialog(__Dymola_loadSelector(filter = "Text files (*.txt; *.dat)", caption = "Open file in which Real parameters are present")));
342 input Integer lineNumber(min = 1) "Number of line to read";
343 output String string "Line of text";
344 output Boolean endOfFile "If true, end-of-file was reached when trying to read line";
345
346 external "C" string= ReadLine(fileName, lineNumber, endOfFile);
347 annotation(Include = "
348#ifndef ReadLine_C
349#define ReadLine_C
350# include <stdio.h>
351# include <string.h>
352extern void ModelicaFormatError(const char* string,...);
353extern char* ModelicaAllocateString(size_t len);
354#ifndef MAXLEN
355#define MAXLEN 133
356#endif
357const char* ReadLine(const char *fileName, int lineNumber, int* endOfFile)
358{
359 FILE* fp;
360 char c, strline[MAXLEN];
361 char* line;
362 int iLine;
363 size_t lineLen;
364 if ((fp=fopen(fileName,\"r\")) == NULL)
365 {
366 ModelicaFormatError(\"ReadLine: File %s not found!\\n\",fileName);
367 return \"\";
368 }
369 iLine=0;
370 while (++iLine <= lineNumber)
371 {
372 c=fgetc(fp);
373 lineLen=1;
374 while (c != '\\n' && c != '\\r' && c != EOF)
375 {
376 if (lineLen < MAXLEN)
377 {
378 strline[lineLen-1]=c;
379 c=fgetc(fp);
380 lineLen++;
381 }
382 else
383 {
384 fclose(fp);
385 ModelicaFormatError(\"ReadLine: Line %d of file %s truncated!\\n\",iLine,fileName);
386 return \"\";
387 }
388 }
389 if (c == EOF && iLine < lineNumber)
390 {
391 fclose(fp);
392 line=ModelicaAllocateString(0);
393 *endOfFile=1;
394 return line;
395 }
396 }
397 fclose(fp);
398 strline[lineLen-1]='\0';
399 line=ModelicaAllocateString(lineLen);
400 strcpy(line, strline);
401 *endOfFile=0;
402 return line;
403}
404#endif
405", Documentation(info = "<html>
406<h4>Syntax</h4>
407<blockquote><pre>
408(string, endOfFile) = <b>readLine</b>(fileName, lineNumber)
409</pre></blockquote>
410<h4>Description</h4>
411<p>
412Function <b>readLine</b>(..) opens the given file, reads enough of the
413content to get the requested line, and returns the line as a string.
414Lines are separated by LF or CR-LF; the returned string does not
415contain the line separator.
416</p>
417<p>
418If lineNumber > countLines(fileName), an empty string is returned
419and endOfFile=true. Otherwise endOfFile=false.
420</p>
421</html>"));
422 end readLineWithoutCache;
423
424 annotation(uses(Modelica(version="3.2.2")));
425end KeyWordIO;