Universität Paderborn - Home Universität Paderborn
Die Universität der Informationsgesellschaft

Generating Software from Specifications WS 2013/14 - File CalendarCheckSol.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, and performs some checks and computations
as required in Assignment 4, Exercise 11.

The following files contain correct input for the processor
~O~<cal.ok~>~{~-
1.11.     20:00  "Theater"
Thu       14:15  "GSS lecture"
1.5.	  9:15	 "Meeting"
Weekday   12:05  "Dinner in Palmengarten"
Mon, Thu         "Dean's office"
31.12.    23:59  "Jahresende"
12/31     23:59  "End of year"
May 1            "Labour Day"
Weekday+Sat      "Work"
-- end of file
~}
~O~<cal1.ok~>~{~-
Thu   10:00 - 10:15 "Have a break"
~}

The following files contain erroneous input for the processor
~O~<cal1.err~>~{~-
1.11  20:00 "A"
1.11. 24:01 "B"
1.11. 20:00 "aa
thu   20:00 "A"
33.12. 20:00 "A"
~}
~O~<cal2.err~>~{~-
12.13. 20:00 "A"
12/33  20:00 "A"
13/12  20:00 "A"
Mai 1            "Labour Day"
- 
~}
~O~<cal3.err~>~{~-
31.4.  9:00  "not in April"
1.13.  9:00  "not in any year"
~}

The following file specifies the concrete syntax:
~O~<Calendar.con~>~{
Calendar:       Entries.
Entries:	Entries Entry / Entry.

Entry:		DateDescr When Description.

DateDescr:	Date / DayNames / GeneralPattern.
Date:           DayNum '.' MonNum '.' /
                MonNum '/' DayNum /
		Month DayNum.

DayNum:         Integer.
MonNum:         Integer.

DayNames:       DayNames ',' DayName / DayName.
DayName:        Day.

GeneralPattern:	SimplePattern Modifier / SimplePattern.
SimplePattern:	'Weekday' / 'Weekend'.
Modifier:       '+' DayNames / '-' DayNames.

When:           Time / Time '-' Time / .
~}

The non-literal tokens are specified as follows:
~O~<Calendar.gla~>~{
Description: C_STRING_LIT
Integer:     PASCAL_INTEGER
Day:         $Mon|Tue|Wed|Thu|Fri|Sat|Sun [mkDay]
Time:        $(([0-9]|1[0-9]|2[0-3]):[0-5][0-9]) [mkTime]
	     ADA_COMMENT
Month:       $Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec [mkMonth]
~}


A mapping specifications as described in the lecture material:
~O~<CalendarChecks.map~>~{
MAPSYM
Pattern ::= GeneralPattern SimplePattern.
MAPRULE
Date:           DayNum '.' MonNum '.' < $1 $2 >.
Date:           MonNum '/' DayNum     < $2 $1 >.
~}
The target rule of the rule mapping:
~O~<MapTarget.lido~>~{
RULE: Date ::= DayNum MonNum END;
~}

The correctness of time spans is checked.
~O~<CalendarChecks1.lido~>~{
RULE: When ::= Time '-' Time COMPUTE
  IF (LE (Time[2], Time[1]),
     message (ERROR, "wrong time span", 0, COORDREF));
END;
~}

Attributes <code>DayNum.val</code> and <code>MonNum.val</code> are
introduced to propagate the values of the
terminal <code>Integer</code> into a context where they can be compared:
~O~<CalendarChecks2.lido~>~{
ATTR val:int;
RULE: DayNum ::= Integer COMPUTE DayNum.val = Integer; END;
RULE: MonNum ::= Integer COMPUTE MonNum.val = Integer; END;

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;
~}

INSERT SOLUTIONS OF ASSIGNMENT 4 HERE:
Use a single FunnelWeb macro for each subtask.

Subtask 1:
Store and print the number of days in the year:
THe computation has to be inserted into two RULE contexts:
~O~<CalCheck-1.nolido~>~{
SYMBOL Date: dayNum: int;
RULE: Date ::= DayNum MonNum COMPUTE
  Date.dayNum = dayInYear (DayNum.val, MonNum.val);
  printf ("Day number %d in line %d\n", Date.dayNum, LINE);
END;
RULE: Date ::= Month DayNum COMPUTE
  Date.dayNum = dayInYear (DayNum.val, Month);
  printf ("Day number %d in line %d\n", Date.dayNum, LINE);
END;
~}

The output for cal.ok is
~O~<cal.out1~>~{
Day number 306 in line 1
Day number 122 in line 3
Day number 366 in line 6
Day number 366 in line 7
Day number 122 in line 8
~}

Subtask 2:
Compute the minimal date that occurs in the input, 
by explicit bottom-up propagation:

The suggested macro definitions for MIN and I400 are
inserted into Calendar.h further down.

A attribute name minDate is introduced.
It is associated to each nonterminal from which Date can be derived, i.e.
DateDescr, Entry, Entries.
Each rule with one of these nonterminals on the left-hand side
that attribute is computed.
2 of the 3 alternative rules for DateDescr do NOT derive Date;
there the attribute is set to a big default value, calling I400.
The minimum day is printed in the Calendar context:
~O~<CalCheck-2.nolido~>~{
ATTR minDate: int;
RULE: DateDescr ::= Date COMPUTE
  DateDescr.minDate = Date.dayNum;
END;
RULE: DateDescr ::= DayNames COMPUTE
  DateDescr.minDate = I400(); /* big number indicates no day in year */
END;
RULE: DateDescr ::= Pattern COMPUTE
  DateDescr.minDate = I400(); /* big number indicates no day in year */
END;
RULE: Entry ::= DateDescr When Description COMPUTE
  Entry.minDate = DateDescr.minDate;
END;
RULE: Entries ::= Entry COMPUTE
  Entries.minDate = Entry.minDate;
END;
RULE: Entries ::= Entries Entry COMPUTE
  Entries[1].minDate = MIN (Entries[2].minDate, Entry.minDate);
END;
RULE: Calendar ::= Entries COMPUTE
  IF (EQ (Entries.minDate, I400()),
    printf ("There is no concrete day in the list.\n"),
    printf ("The earliest day in the list is %d\n",
            Entries.minDate));
END;
~}

The output for cal.ok is
~O~<cal.out2~>~{
Day number 306 in line 1
Day number 122 in line 3
Day number 366 in line 6
Day number 366 in line 7
Day number 122 in line 8
The earliest day in the list is 122
~}

Subtask 3:
Deactivate the solution of subtask 2.
Use a CONSTITUENTS-WITH construct to compute the earliest date
~O~<CalCheck-3.nolido~>~{~-
ATTR minDate: int;
RULE: Calendar ::= Entries COMPUTE
  Calendar.minDate = CONSTITUENTS Date.dayNum
     WITH (int, MIN, IDENTICAL, I400);
  IF (EQ (Calendar.minDate, I400()),
    printf ("There is no concrete day in the list.\n"),
    printf ("The earliest day in the list is %d\n",
            Calendar.minDate));
END;
~}
The output for cal.ok is the same as for subtask 2.

Subtask 4:
Propagate the minimal date down to each Date and output it there:
~O~<CalCheck-4.nolido~>~{
ATTR earliestDate: int;
RULE: Calendar ::= Entries COMPUTE
  Entries.earliestDate = Calendar.minDate;
END;
RULE: Entries ::= Entries Entry COMPUTE
  Entries[2].earliestDate = Entries[1].earliestDate;
  Entry.earliestDate = Entries[1].earliestDate;
END;
RULE: Entries ::= Entry COMPUTE
  Entry.earliestDate = Entries[1].earliestDate;
END;
RULE: Entry ::= DateDescr When Description COMPUTE
  DateDescr.earliestDate = Entry.earliestDate;
END;
RULE: DateDescr ::= Date COMPUTE
  Date.earliestDate = DateDescr.earliestDate;
END;
RULE: Date ::= DayNum MonNum COMPUTE
  IF (EQ (Date.dayNum, Date.earliestDate),
    printf ("Ealiest day %d in line %d\n", Date.dayNum, LINE));
END;
RULE: Date ::= Month DayNum COMPUTE
  IF (EQ (Date.dayNum, Date.earliestDate),
    printf ("Ealiest day %d in line %d\n", Date.dayNum, LINE));
END;
~}

The output for cal.ok is
~O~<cal.out4~>~{
Day number 306 in line 1
Day number 122 in line 3
Day number 366 in line 6
Day number 366 in line 7
Day number 122 in line 8
The earliest day in the list is 122
Ealiest day 122 in line 3
Ealiest day 122 in line 8
~}

Subtask 5:
Deactivate the solution of subtask 4.
Propagate the minimal date down to every Date using an 
INCLUDING construct, and output it there:
The print computation has to be specified in both rules for Date:
~O~<CalCheck-5.nolido~>~{
RULE: Date ::= DayNum MonNum COMPUTE
  IF (EQ (Date.dayNum, INCLUDING Calendar.minDate),
    printf ("Ealiest day %d in line %d\n", Date.dayNum, LINE));
END;
RULE: Date ::= Month DayNum COMPUTE
  IF (EQ (Date.dayNum, INCLUDING Calendar.minDate),
    printf ("Ealiest day %d in line %d\n", Date.dayNum, LINE));
END;
~}

The output for cal.ok is the same as for subtask 4.

Subtask 6:
Deactivate the solutions of subtasks 1 to 5.
Use Symbol computations wherever possible.
~O~<CalCheck-6.lido~>~{
SYMBOL Date: dayNum: int;
RULE: Date ::= DayNum MonNum COMPUTE
  Date.dayNum = dayInYear (DayNum.val, MonNum.val);
END;
RULE: Date ::= Month DayNum COMPUTE
  Date.dayNum = dayInYear (DayNum.val, Month);
END;
SYMBOL Date COMPUTE
  printf ("Day number %d in line %d\n", THIS.dayNum, LINE);
END;

ATTR minDate: int;
SYMBOL Calendar COMPUTE
  SYNT.minDate = CONSTITUENTS Date.dayNum
     WITH (int, MIN, IDENTICAL, I400);
  IF (EQ (SYNT.minDate, I400()),
    printf ("There is no concrete day in the list.\n"),
    printf ("The earliest day in the list is %d\n",
            SYNT.minDate));
END;

SYMBOL Date COMPUTE
  IF (EQ (THIS.dayNum, INCLUDING Calendar.minDate),
    printf ("Ealiest day %d in line %d\n", THIS.dayNum, LINE));
END;
~}
The output for cal.ok is the same as for subtask 5.

~O~<Calendar.head~>==~{
#include "csm.h"
#include "Calendar.h"
~}
~O~<Calendar.h~>==~{
extern void mkDay (char *, int, int *, int*);
extern void mkTime (char *, int, int *, int*);
extern void mkMonth (char *, int, int *, int*);

extern int dayInYear (int day, int mon);
extern int wrongDate (int day, int mon);

#ifndef MIN
#define MIN(a,b) ((a)<(b)?(a):(b))
#define I400() (400)
#endif
~}
~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 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;
}

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;
    }
}

/* day in year =  day + (month-1)*28 + delta[month-1] 
   Delta values for a
non-leap year:
int delta[] = {0,3,3,6,8,11,13,16,19,21,24,26,29};
leap year:
int delta[] = {0,3,4,7,9,12,14,17,20,22,25,27,30};
*/
int delta[] = {0,3,4,7,9,12,14,17,20,22,25,27,30};

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;
}
~}

Generiert mit Camelot | Probleme mit Camelot? | Geändert am: 24.11.2013