Generating Software from Specifications WS 2013/14 - File CalTableSol.fw
@=~ ~p maximum_input_line_length = infinity This file contains the specification of a calendar processor. It reads a sequence of appointments, represents them by a tree structure, checks some consistency conditions, and outputs a table in HTML containing the input data. The following file contains a correct input file for the processor such that almost every language construct occurs at least once: ~O~<cal.ok~>~{~- 1.11. 20:00 "Theater" Thu 11:15 - 12:45 "GSS lecture" Weekday - Mon 12:05 "Dinner in Palmengarten" Mon, Thu "Dean's office" 31.12. 23:59 "Jahresende" 12/31 23:59 "End of year" 25.5. 11:15 "GSS" -- Lecture assessment May 1 "Labour Day" 1.1. 12:00 - 12:01 "Flash1" 2/28 9:00 "end of Feb" 31.12. 23:59 "Schluss" ~} The following input file exhibits an error in each line: ~O~<cal.err~>~{~- 1.1. 12:00 - 12:00 "wrong time span" 1.1. 12:00 - 11:59 "wrong time span" 2/29 9:00 "not this year" 31.4. 9:00 "not in April" 1.13. 9:00 "not in any year" M 1 "wrong month" 2.5. 24:00 "wrong hour" 4.5. 11:15 "wrong comment" - ~} The input for our calendar processor is a sequence of entries. Each entry consists of a date description and an event that is to happen at that date. The dates may be specified in several forms: ~$~<Entries con~>==~{ Calendar: Entry+. Entry: DateDescr Event. DateDescr: Date / DayNames / GeneralPattern. ~} Concrete dates specify a month and a day in that month. Month and day can be given by numbers, using two notational variants. The third variant allows to write the name of a month. It will be checked that a day of the given number exists in the given month: ~$~<Date con~>==~{ Date: DayNum '.' MonNum '.' / MonNum '/' DayNum / Month DayNum. DayNum: Integer. MonNum: Integer. ~} An event may occur at certain days of the week and obey the same pattern every week. In these cases the date description either enumerate the names of the days of the week, or specify the days of the weekend or the non-weekend days. In the latter cases further days may be added or subtracted: ~$~<Patterns con~>==~{ DayNames: DayName / DayNames ',' DayName. DayName: Day. GeneralPattern: SimplePattern / SimplePattern Modifier. SimplePattern: 'Weekday' / 'Weekend'. Modifier: '+' DayNames / '-' DayNames. ~} An event is described by a time or a time span and an explaining string. The start time of a time span must be earlier than the end time: ~$~<Event con~>==~{ Event: When Description / Description. When: Time / Time '-' Time. ~} The following file comprises the concrete syntax: ~O~<Calendar.con~>~{ ~<Entries con~> ~<Date con~> ~<Patterns con~> ~<Event con~> ~} The language has the following basic symbols. Event descriptions are string literals in C notation. Integral numbers are used to describe months and days. The days of the week are named by 3-letter abbreviations. They are processed like predefined identiers and stored in the identifier table by mkidn. (On output they can be copied from there.) The abbreviated names of months are mapped to integral numbers by mkMonth. The language allows to write line comments s in Ada. The canned description C_STRING_LIT applies the token processor mkstr; it is overridden by c_mkstr, in order to store string values without delimiting double quotes. ~O~<Calendar.gla~>~{ Description: C_STRING_LIT [c_mkstr] Integer: PASCAL_INTEGER Day: $Mon|Tue|Wed|Thu|Fri|Sat|Son [mkidn] Month: $Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec [mkMonth] Time: $(([0-9]|1[0-9]|2[0-3]):[0-5][0-9]) [mkstr] ADA_COMMENT ~} The following abstract syntax is systematically rewritten from the concrete syntax. It deviates only in the following aspects: Alternatives in the concrete syntax are separate rules in the abstract syntax. The sequence of entries is expressed by a LISTOF production. Two unifying mappings are described below. This file containing the abstract syntax could be completely omitted from the specification, Eli then would generate it from the remaining specification. However, it is recommended to keep it, and to use it as a reference when spcifying computations in tree contexts. It is strongly recommended NOT to write rule computations into this file. Instead, rules may be copied to new fragments and extended by computatons there. That for different computational tasks several copies of a rule may be used, in order to separate the task from each other. ~O~<Calendar.lido~>~{ RULE: Calendar LISTOF Entry END; RULE: Entry ::= DateDescr Event END; RULE: DateDescr ::= Date END; RULE: DateDescr ::= DayNames END; RULE: DateDescr ::= Pattern END; RULE: Date ::= DayNum MonNum END; RULE: Date ::= Month DayNum END; RULE: DayNum ::= Integer END; RULE: MonNum ::= Integer END; RULE: DayNames ::= DayName END; RULE: DayNames ::= DayNames ',' DayName END; RULE: DayName ::= Day END; RULE: Pattern ::= Pattern Modifier END; RULE: Pattern ::= 'Weekday' END; RULE: Pattern ::= 'Weekend' END; RULE: Modifier ::= '+' DayNames END; RULE: Modifier ::= '-' DayNames END; RULE: Event ::= When Description END; RULE: Event ::= Description END; RULE: When ::= Time END; RULE: When ::= Time '-' Time END; ~} The abstract syntax unifies the two pattern nonterminals of the concrete syntax, and it unifies the European and the US way of writing the day of a month: ~O~<Calendar.map~>~{ MAPSYM Pattern ::= GeneralPattern SimplePattern. MAPRULE Date: DayNum '.' MonNum '.' < $1 $2 >. Date: MonNum '/' DayNum < $2 $1 >. ~} The following C module implements the three token coding functions mkTime, mkMonth, and mkDay which compute an interger for the given string (The number of the minute in the day, of the month in the year, and of the day in the week. Only mkMonth is used in the scanner specification above, because times are stored as strings and days are stored as identifiers. The function Time2Int computes the number of the minute in the day, as mkTime does; it is only used to simplify the comparison of times. ~O~<Calendar.HEAD~>==~{ #include "Calendar.h" ~} ~O~<Calendar.h~>==~{ extern void mkTime (char *, int, int *, int*); extern void mkMonth (char *, int, int *, int*); extern void mkDay (char *, int, int *, int*); extern int Time2Int (char *t); ~} ~O~<Calendar.c~>==~{ #include <string.h> #include "Calendar.h" void mkDay (char *d, int l, int *c, int *i) { switch (d[0]) { case 'F': *i = 5; break; case 'M': *i = 1; break; case 'W': *i = 3; break; case 'S': *i = (d[1] == 'a'? 6 : 7); break; case 'T': *i = (d[1] == 'u' ? 2 : 4); break; } } void mkMonth (char *m, int l, int *c, int *i) { switch (m[0]) { case 'A': *i = (m[1] == 'p' ? 4 : 8); break; case 'D': *i = 12; break; case 'F': *i = 2; break; case 'J': *i = (m[1] == 'a' ? 1 : (m[2] == 'n' ? 6 : 7)); break; case 'M': *i = (m[2] == 'r' ? 3 : 5); break; case 'N': *i = 11; break; case 'O': *i = 10; break; case 'S': *i = 9; break; } } int Time2Int (char *t) { char *colon = strchr (t, ':'); int hours, mins; *colon = '\0'; hours = atoi (t); mins = atoi (colon + 1); *colon = ':'; return hours*60 + mins; } void mkTime (char *t, int l, int *c, int *i) { char *colon = strchr (t, ':'); int hours, mins; *colon = '\0'; hours = atoi (t); mins = atoi (colon + 1); *colon = ':'; *i = hours*60 + mins; } ~} An eror message is reported if the end time of a time span is not later than the start time: ~O~<TimeRange.lido~>==~{ RULE: When ::= Time '-' Time COMPUTE IF (LE (Time2Int (StringTable(Time[2])), Time2Int (StringTable(Time[1]))), message (ERROR, "end must later than begin", 0, COORDREF)); END; ~} An eror message is reported if the specified month does not exist in a year or if the specified day does not exist in the given month: ~O~<DateChk.lido~>==~{ RULE: Date ::= DayNum MonNum COMPUTE IF (wrongDate (DayNum.val, MonNum.val), message (ERROR, "wrong date", 0, COORDREF)); END; RULE: Date ::= Month DayNum COMPUTE IF (wrongDate (DayNum.val, Month), message (ERROR, "wrong date", 0, COORDREF)); END; SYMBOL DayNum, MonNum: val: int; RULE: DayNum ::= Integer COMPUTE DayNum.val = Integer; END; RULE: MonNum ::= Integer COMPUTE MonNum.val = Integer; END; ~} The following C module implements two functions wrongDate and dayInYear. The latter maps a month number and a day number to the number of the day in the year; the former checks whether the given day number and month number describe an existing day. Both functions use a formula that assumes that every month has at least 28 days; the differences to the reals number of days are accumultaed in the array delta: ~O~<DateChk.head~>==~{ #include "DateChk.h" ~} ~O~<DateChk.h~>==~{ extern int wrongDate (int day, int mon); extern int dayInYear (int day, int mon); ~} ~O~<DateChk.c~>==~{ #include "DateChk.h" /* day in year = day + (month-1)*28 + delta[month-1] */ int delta[] = {0,3,3,6,8,11,13,16,19,21,24,26,29}; int dayInYear (int day, int month) { if (day <= 0 || day > 31 || month <= 0 || month > 12) return 1; return day + (month-1)*28 + delta[month-1]; } int wrongDate (int day, int month) { if (day <= 0 || day > 31 || month <= 0 || month > 12) return 1; if (dayInYear (day, month) > month*28 + delta[month]) return 1; return 0; } ~} This calendar processor is to produce output in form of an HTML file. The input data is to be presented as a table that has one row per entry. The table has three columns, containing the date, the time or time span, if any, and the event description. Here is the output for the correct input file given above: ~O~<cal.html~>~{ <html> <body> <h3>Calendar Events</h3> <table border=1> <tr> <td><b>Date</b></td><td><b>Time</b></td><td><b>Description</b></td> </tr> <tr> <td>1.11.</td><td>20:00</td><td>Theater</td> </tr> <tr> <td>Thu</td><td>11:15 - 12:45</td><td>GSS lecture</td> </tr> <tr> <td>Werktage - Mon</td><td>12:05</td><td>Dinner in Palmengarten</td> </tr> <tr> <td>Mon, Thu</td><td></td><td>Dean's office</td> </tr> <tr> <td>31.12.</td><td>23:59</td><td>Jahresende</td> </tr> <tr> <td>31.12.</td><td>23:59</td><td>End of year</td> </tr> <tr> <td>25.5.</td><td>11:15</td><td>GSS</td> </tr> <tr> <td>1.5.</td><td></td><td>Labour Day</td> </tr> <tr> <td>1.1.</td><td>12:00 - 12:01</td><td>Flash1</td> </tr> <tr> <td>28.2.</td><td>9:00</td><td>end of Feb</td> </tr> <tr> <td>31.12.</td><td>23:59</td><td>Schluss</td> </tr> </table> </body> </html> ~} The output is produced using PTG functions in computations in tree contexts. In order to make useful prefined PTG patterns, and some functions on strings available, we use the following components of Eli's libraries: ~O~<LibModules.specs~>~{ $/Tech/Strings.specs $/Output/PtgCommon.fw ~} TASK: Specify PTG patterns for the intended output. The following pattern describes the overall structure of the output file. The rows of the table are inserted at the only insertion point: ~O~<Frame.ptg~>~{ Frame: "<html>\n" "<body>\n" "<h3>Calendar Events</h3>\n" "<table border=1>\n" "<tr>\n" " <td><b>Date</b></td><td><b>Time</b></td><td><b>Description</b></td>\n" "</tr>\n" $1 /* table rows */ "</table>\n" "</body>\n" "</html>\n" ~} The following patterns are used to compose rows from data elements: ~O~<Table.ptg~>~{ TableRow: " <tr>\n " $1 /* data elements */ "\n </tr>\n" TableData: "<td>" $1 /* data */ "</td>" ~} The notations of days in a month, time spans, and modifiers are described by the following patterns: ~O~<DateTime.ptg~>~{ Date: $1 int /* day */ "." $2 int /* month */ "." Span: $1 string " - " $2 string Minus: " - " $1 Plus: " + " $1 ~} Most symbols of the grammar will have an attribute named code which will take a PTG node as value. The following ATTR construct allows to just use such attributes without further specification of their type. The attribute Calendar.Code, finally holds the PTG structure representing the complete output. The call of PTGOutFile prints that output to a file, the name of which is formed by concatenating ".html" to the name of the input file: ~O~<PtgOutput.lido~>~{ ATTR Code: PTGNode; SYMBOL Calendar COMPUTE PTGOutFile (CatStrStr (SRCFILE, ".html"), SYNT.Code); END; ~} The following specification is needed to make the name of the input file available by SRCFILE: ~O~<InputFileName.head~>~{ #include "source.h" ~} TASK: Specify computations in tree contexts that use the functions of the specified PTG patterns to solve the following subtasks: All table rows are collected and inserted into an instance of the Frame pattern: ~O~<Rows.lido~>~{ SYMBOL Calendar COMPUTE SYNT.Code = PTGFrame (CONSTITUENTS Entry.Code WITH (PTGNode, PTGSeq, IDENTICAL, PTGNull)); END; RULE: Entry ::= DateDescr Event COMPUTE Entry.Code = PTGTableRow (PTGSeq (DateDescr.Code, Event.Code)); END; ~} The date is transformed into a data element: ~O~<Dates.lido~>~{ RULE: DateDescr ::= Date COMPUTE DateDescr.Code = PTGTableData (Date.Code); END; RULE: Date ::= DayNum MonNum COMPUTE Date.Code = PTGDate (DayNum.val, MonNum.val); END; RULE: Date ::= Month DayNum COMPUTE Date.Code = PTGDate (DayNum.val, Month); END; ~} Sequences of DayNames are collected in a single data element. The name of a day is read as a string from the StringTable: ~O~<DayNames.lido~>~{ RULE: DateDescr ::= DayNames COMPUTE DateDescr.Code = PTGTableData (DayNames.Code); END; RULE: DayNames ::= DayName COMPUTE DayNames.Code = DayName.Code; END; RULE: DayNames ::= DayNames ',' DayName COMPUTE DayNames[1].Code = PTGCommaSeq (DayNames[2].Code, DayName.Code); END; RULE: DayName ::= Day COMPUTE DayName.Code = PTGAsIs (StringTable (Day)); END; ~} Weekly patterns are composed: ~O~<Patterns.lido~>~{ RULE: DateDescr ::= Pattern COMPUTE DateDescr.Code = PTGTableData (Pattern.Code); END; RULE: Pattern ::= Pattern Modifier COMPUTE Pattern[1].Code = PTGSeq (Pattern[2].Code, Modifier.Code); END; RULE: Pattern ::= 'Weekday' COMPUTE Pattern.Code = PTGAsIs ("Werktage"); END; RULE: Pattern ::= 'Weekend' COMPUTE Pattern.Code = PTGAsIs ("Wochenende"); END; RULE: Modifier ::= '+' DayNames COMPUTE Modifier.Code = PTGPlus (DayNames.Code); END; RULE: Modifier ::= '-' DayNames COMPUTE Modifier.Code = PTGMinus (DayNames.Code); END; ~} Two data elements are produced for the time or time span and for the event description: ~O~<Events.lido~>~{ RULE: Event ::= Description COMPUTE Event.Code = PTGSeq (PTGTableData (PTGNULL), PTGTableData (PTGId (Description))); END; RULE: Event ::= When Description COMPUTE Event.Code = PTGSeq (PTGTableData (When.Code), PTGTableData (PTGId (Description))); END; RULE: When ::= Time COMPUTE When.Code = PTGAsIs (StringTable(Time)); END; RULE: When ::= Time '-' Time COMPUTE When.Code = PTGSpan (StringTable (Time[1]), StringTable (Time[2])); END; ~} Test your specification comprehensively and use a browser to check the resulting HTML files.
Generiert mit Camelot | Probleme mit Camelot? | Geändert am: 18.12.2013