home *** CD-ROM | disk | FTP | other *** search
/ Power-Programmierung / CD1.mdf / magazine / drdobbs / 1989 / 06 / asmstr.asc < prev    next >
Text File  |  1989-05-27  |  23KB  |  619 lines

  1. _STRUCTURED PROGRAMMING COLUMN_
  2. by Jeff Duntemann
  3.  
  4. [LISTING ONE]
  5.  
  6. { Calendar unit demo program }
  7. { Jeff Duntemann  -- 2/3/89  }
  8.  
  9.  
  10. PROGRAM CalTest;
  11.  
  12.  
  13. USES DOS,Crt,    { Standard Borland units }
  14.      Screens,    { Given in DDJ 4/89 }
  15.      Calendar;   { Given in DDJ 6/89 }
  16.  
  17. CONST
  18.   YellowOnBlue = $1E; { Text attribute; yellow chars on blue background }
  19.   CalX         = 25;
  20.   CalY         = 5;
  21.  
  22.  
  23. VAR
  24.   MyScreen   : ScreenPtr;  { Type exported by Screens unit }
  25.   WorkScreen : Screen;     { Type exported by Screens unit }
  26.   Ch         : Char;
  27.   Quit       : Boolean;
  28.   ShowFor    : DateTime;   { Type exported by DOS unit }
  29.   I          : Word;       { Dummy; picks up dayofweek field in GetDate }
  30.  
  31.  
  32. BEGIN
  33.   MyScreen := @WorkScreen;    { Create a pointer to WorkScreen }
  34.   InitScreen(MyScreen,True);
  35.   ClrScreen(MyScreen,ClearAtom);     { Clear the entire screen }
  36.   Quit := False;
  37.  
  38.   WITH ShowFor DO    { Start with clock date }
  39.     GetDate(Year,Month,Day,I);
  40.  
  41.   ShowCalendar(MyScreen,ShowFor,CalX,CalY,YellowOnBlue);
  42.  
  43.   REPEAT                    { Until Enter is pressed: }
  44.     IF Keypressed THEN      { If a keystroke is detected }
  45.       BEGIN
  46.         Ch := ReadKey;      { Pick up the keystroke }
  47.         IF Ord(Ch) = 0 THEN { See if it's an extended keystroke }
  48.           BEGIN
  49.             Ch := ReadKey;  { If so, pick up scan code }
  50.             CASE Ord(Ch) OF { and parse it }
  51.               72 : Pan(MyScreen,Up,1);   { Up arrow }
  52.               80 : Pan(MyScreen,Down,1); { Down arrow }
  53.               75 : BEGIN                 { Left arrow; "down time" }
  54.                      WITH ShowFor DO
  55.                        IF Month = 1 THEN
  56.                          BEGIN
  57.                            Month := 12;
  58.                            Dec(Year)
  59.                          END
  60.                        ELSE Dec(Month);
  61.                      ShowCalendar(MyScreen,ShowFor,CalX,CalY,YellowOnBlue);
  62.                    END;
  63.               77 : BEGIN                 { Right arrow; "up time" }
  64.                      WITH ShowFor DO
  65.                        IF Month = 12 THEN
  66.                          BEGIN
  67.                            Month := 1;
  68.                            Inc(Year)
  69.                          END
  70.                        ELSE Inc(Month);
  71.                      ShowCalendar(MyScreen,ShowFor,CalX,CalY,YellowOnBlue);
  72.                    END;
  73.             END { CASE }
  74.           END
  75.         ELSE     { If it's an ordinary keystroke, test for quit: }
  76.           IF Ch = Chr(13) THEN Quit := True
  77.       END;
  78.   UNTIL Quit;
  79.   ClrScreen(MyScreen,ClearAtom)  { All this stuff's exported by Screens }
  80. END.
  81.  
  82.  
  83. [LISTING TWO]
  84.  
  85. {--------------------------------------------------------------}
  86. {                         CALENDAR                             }
  87. {                                                              }
  88. {          Text calendar for virtual screen platform           }
  89. {                                                              }
  90. {                                    by Jeff Duntemann KI6RA   }
  91. {                                    Turbo Pascal 5.0          }
  92. {                                    Last modified 2/3/89      }
  93. {--------------------------------------------------------------}
  94.  
  95. UNIT Calendar;
  96.  
  97. INTERFACE
  98.  
  99. USES DOS,       { Standard Borland unit }
  100.      TextInfo,  { Given in DDJ 3/89     }
  101.      Screens,   { Given in DDJ 4/89     }
  102.      CalCalc;   { Given in DDJ 6/89 courtesy Michael Covington }
  103.  
  104. TYPE
  105.   DaysOfWeek = (Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday);
  106.   Months     = (January,February,March,April,May,June,July,
  107.                 August,September,October,November,December);
  108.  
  109.  
  110. PROCEDURE ShowCalendar(Target    : ScreenPtr;
  111.                        ShowFor   : DateTime;
  112.                        CalX,CalY : Integer;
  113.                        Attribute : Byte);
  114.  
  115.  
  116. IMPLEMENTATION
  117.  
  118. TYPE
  119.   String10 = STRING[10];
  120.  
  121. CONST
  122.   MonthNames : ARRAY[January..December] OF String10 =
  123.   ('January','February', 'March','April','May','June','July',
  124.    'August', 'September','October','November','December');
  125.   Days : ARRAY[January..December] OF Integer =
  126.   (31,28,31,30,31,30,31,31,30,31,30,31);
  127.  
  128. {$L CALBLKS}
  129. {$F+} PROCEDURE CalFrame; EXTERNAL;
  130.       PROCEDURE Caldata;  EXTERNAL;
  131. {$F-}
  132.  
  133. {$L BLKBLAST}
  134. {$F+}
  135. PROCEDURE BlkBlast(ScreenEnd,StoreEnd : Pointer;
  136.                    ScreenX,ScreenY    : Integer;
  137.                    ULX,ULY            : Integer;
  138.                    Width,Height       : Integer;
  139.                    Attribute          : Byte;
  140.                    DeadLines          : Integer;
  141.                    TopStop            : Integer);
  142.           EXTERNAL;
  143. {$F-}
  144.  
  145.  
  146.  
  147. FUNCTION IsLeapYear(Year : Integer) : Boolean;
  148.  
  149. { Works from 1901 - 2199 }
  150.  
  151. BEGIN
  152.   IsLeapYear := False;
  153.   IF (Year MOD 4) = 0 THEN IsLeapYear := True
  154. END;
  155.  
  156.  
  157.  
  158.  
  159. PROCEDURE FrameCalendar(Target    : ScreenPtr;
  160.                         CalX,CalY : Integer;
  161.                         Attribute : Byte;
  162.                         StartDay  : DaysOfWeek;
  163.                         DayCount  : Integer);
  164.  
  165. TYPE
  166.   PointerMath = RECORD
  167.                   CASE BOOLEAN OF
  168.                     True  : (APointer : Pointer);
  169.                     False : (OfsWord  : Word;
  170.                              SegWord  : Word)
  171.                 END;
  172.  
  173. VAR
  174.   DataPtr    : Pointer;
  175.   FudgeIt    : PointerMath;
  176.   DayInset   : Word;
  177.   DayTopStop : Word;
  178.  
  179. BEGIN
  180.   { DayInset allows is to specify which day of the week the first of the }
  181.   { month falls.  It's an offset into the block containing day figures   }
  182.   DayInset := (7-Ord(StartDay))*4;
  183.   { DayTopStop allows us to specify how many days to show in the month.  }
  184.   DayTopStop := 28+(DayCount*4)-DayInset;
  185.   BlkBlast(Target,@CalFrame,    { Display the calendar frame            }
  186.            VisibleX,VisibleY,   { Genned screen size from TextInfo unit }
  187.            CalX,CalY,           { Show at specified coordinates         }
  188.            29,17,               { Size of calendar frame block          }
  189.            Attribute,           { Attribute to use for calendar frame   }
  190.            0,                   { No interspersed empty lines           }
  191.            0);                  { No topstop; show the whole thing.     }
  192.  
  193.   WITH FudgeIt DO { FudgeIt is a free union allowing pointer arithmetic }
  194.     BEGIN
  195.       APointer := @CalData;     { Create the pointer to the days block  }
  196.       OfsWord  := OfsWord+DayInset; { Offset into block for start day   }
  197.  
  198.       BlkBlast(Target,APointer,     { Blast the day block over the      }
  199.                VisibleX,VisibleY,   {   calendar frame }
  200.                CalX+1,CalY+5,       { Pos. of days relative to frame    }
  201.                28,6,                { Size of day block }
  202.                Attribute,           { Show days in same color as frame  }
  203.                1,                   { Insert 1 line between block lines }
  204.                DayTopStop)          { Set limit on number of chars to   }
  205.     END                             { be copied from block to control   }
  206. END;                                { how many days shown for a month   }
  207.  
  208.  
  209.  
  210.  
  211. PROCEDURE ShowCalendar(Target    : ScreenPtr;
  212.                        ShowFor   : DateTime;
  213.                        CalX,CalY : Integer;
  214.                        Attribute : Byte);
  215.  
  216. CONST
  217.   NameOffset : ARRAY[January..December] OF Integer =
  218.   (8,8,10,10,11,10,10,9,7,8,8,8);
  219.  
  220. VAR
  221.   StartDay    : DaysOfWeek;
  222.   TargetMonth : Months;
  223.   TargetDay   : Real;
  224.   DaysInMonth : Integer;
  225.  
  226. BEGIN
  227.   { First figure day number since 1980: }
  228.   WITH ShowFor DO TargetDay := DayNumber(Year,Month,1);
  229.   { Then use the day number to calculate day-of-the-week: }
  230.   StartDay := DaysOfWeek(WeekDay(TargetDay)-1);
  231.   TargetMonth := Months(ShowFor.Month-1);
  232.   DaysInMonth := Days[TargetMonth];
  233.   { Test and/or adjust for leap year: }
  234.   IF TargetMonth = February THEN
  235.     IF IsLeapYear(ShowFor.Year) THEN DaysInMonth := 29;
  236.   { Now draw the frame on the virtual screen! }
  237.   FrameCalendar(Target,
  238.                 CalX,CalY,
  239.                 Attribute,
  240.                 StartDay,
  241.                 DaysInMonth);
  242.   { Add the month name and year atop the frame: }
  243.   GotoXY(Target,CalX+NameOffset[TargetMonth],CalY+1);
  244.   WriteTo(Target,MonthNames[TargetMonth]+' '+IntStr(ShowFor.Year,4));
  245. END;
  246.  
  247.  
  248.  
  249. END.
  250.  
  251. [LISTING THREE]
  252.  
  253. UNIT CalCalc;
  254.  
  255. { --- Calendrics --- }
  256.  
  257. { Long-range calendrical package in standard Pascal  }
  258. { Copyright 1985 Michael A. Covington                }
  259.  
  260. INTERFACE
  261.  
  262. function daynumber(year,month,day:integer):real;
  263.  
  264. procedure caldate(date:real; var year,month,day:integer);
  265.  
  266. function weekday(date:real):integer;
  267.  
  268. function julian(date:real):real;
  269.  
  270. IMPLEMENTATION
  271.  
  272.  
  273. function floor(x:real) : real;
  274.   { Largest whole number not greater than x.           }
  275.   { Uses real data type to accommodate large numbers.  }
  276. begin
  277.   if (x < 0) and (frac(x) <> 0) then
  278.     floor := int(x) - 1.0
  279.   else
  280.     floor := int(x)
  281. end;
  282.  
  283.  
  284.  
  285. function daynumber(year,month,day:integer):real;
  286.   { Number of days elapsed since 1980 January 0 (1979 December 31). }
  287.   { Note that the year should be given as (e.g.) 1985, not just 85. }
  288.   { Switches from Julian to Gregorian calendar on Oct. 15, 1582.    }
  289. var
  290.   y,m:   integer;
  291.   a,b,d: real;
  292. begin
  293.   if year < 0 then y := year + 1
  294.               else y := year;
  295.   m := month;
  296.   if month < 3 then
  297.     begin
  298.       m := m + 12;
  299.       y := y - 1
  300.     end;
  301.   d := floor(365.25*y) + int(30.6001*(m+1)) + day - 723244.0;
  302.   if d < -145068.0 then
  303.     { Julian calendar }
  304.     daynumber := d
  305.   else
  306.     { Gregorian calendar }
  307.     begin
  308.       a := floor(y/100.0);
  309.       b := 2 - a + floor(a/4.0);
  310.       daynumber := d + b
  311.     end
  312. end;
  313.  
  314. procedure caldate(date:real; var year,month,day:integer);
  315.   { Inverse of DAYNUMBER; given date, finds year, month, and day.   }
  316.   { Uses real arithmetic because numbers are too big for integers.  }
  317. var
  318.   a,aa,b,c,d,e,z: real;
  319.   y: integer;
  320. begin
  321.   z := int(date + 2444239.0);
  322.   if date < -145078.0 then
  323.     { Julian calendar }
  324.     a := z
  325.   else
  326.     { Gregorian calendar }
  327.     begin
  328.       aa := floor((z-1867216.25)/36524.25);
  329.       a := z + 1 + aa - floor(aa/4.0)
  330.     end;
  331.   b := a + 1524.0;
  332.   c := int((b-122.1)/365.25);
  333.   d := int(365.25*c);
  334.   e := int((b-d)/30.6001);
  335.   day := trunc(b - d - int(30.6001*e));
  336.   if e > 13.5 then month := trunc(e - 13.0)
  337.               else month := trunc(e - 1.0);
  338.   if month > 2 then y := trunc(c - 4716.0)
  339.                else y := trunc(c - 4715.0);
  340.   if y < 1 then year := y - 1
  341.            else year := y
  342. end;
  343.  
  344. function weekday(date:real):integer;
  345.   { Given day number as used in the above routines,   }
  346.   { finds day of week (1 = Sunday, 2 = Monday, etc.). }
  347. var
  348.   dd: real;
  349. begin
  350.   dd := date;
  351.   while dd > 28000.0 do dd:=dd-28000.0;
  352.   while dd < 0 do dd:=dd+28000.0;
  353.   weekday := ((trunc(dd) + 1) mod 7) + 1
  354. end;
  355.  
  356. function julian(date:real):real;
  357.   { Converts result of DAYNUMBER into a Julian date. }
  358. begin
  359.   julian := date + 2444238.5
  360. end;
  361.  
  362. END.  { CalCalc }
  363.  
  364. [LISTING FOUR]
  365.  
  366. ;===========================================================================
  367. ;
  368. ; B L K B L A S T  -  Blast 2D character pattern and attributes into memory
  369. ;
  370. ;===========================================================================
  371. ;
  372. ;     by Jeff Duntemann      3 February 1989
  373. ;
  374. ; BLKBLAST is written to be called from Turbo Pascal 5.0 using the EXTERNAL
  375. ; machine-code procedure convention.
  376. ;
  377. ; This version is written to be used with the SCREENS.PAS virtual screens
  378. ; unit for Turbo Pascal 5.0.  See DDJ for 4/89.
  379. ;
  380. ; Declare the procedure itself as external using this declaration:
  381. ;
  382. ; PROCEDURE BlkBlast(ScreenEnd,StoreEnd : Pointer;
  383. ;                    ScreenX,ScreenY    : Integer;
  384. ;                    ULX,ULY            : Integer;
  385. ;                    Width,Height       : Integer;
  386. ;                    Attribute          : Byte;
  387. ;                    DeadLines          : Integer;
  388. ;                    TopStop            : Integer);
  389. ;           EXTERNAL;
  390. ;
  391. ; The idea is to store a video pattern as an assembly-language external or
  392. ; as a typed constant, and then blast it into memory so that it isn't seen
  393. ; to "flow" down from top to bottom, even on 8088 machines.
  394. ;
  395. ; During the blast itself, the attribute byte passed in the Attribute
  396. ; parameter is written to the screen along with the character information
  397. ; pointed to by the source pointer.  In effect, this means we do a byte-sized
  398. ; read from the source character data, but a word-sized write to the screen.
  399. ;
  400. ; The DeadLines parm specifies how many screen lines to skip between lines of
  401. ; the pattern.  The skipped lines are not disturbed.  TopStop provides a byte
  402. ; count that is the maximum number of bytes to blast in from the pattern.
  403. ; If a 0 is passed in TopStop, the value is ignored.
  404. ;
  405. ; To reassemble BLKBLAST:
  406. ;
  407. ; Assemble this file with MASM or TASM:  "C>MASM BLKBLAST;"
  408. ; (The semicolon is unnecessary with TASM.)
  409. ;
  410. ; No need to relink; Turbo Pascal uses the .OBJ only.
  411. ;
  412. ;========================
  413. ;
  414. ; STACK PROTOCOL
  415. ;
  416. ; This creature puts lots of things on the stack.  Study closely:
  417. ;
  418.  
  419. ONSTACK STRUC
  420. OldBP   DW ?    ;Caller's BP value saved on the stack
  421. RetAddr DD ?    ;Full 32-bit return address.  (This is a FAR proc!)
  422. TopStop DW ?    ;Maximum number of chars to be copied from block pattern
  423. DeadLns DW ?    ;Number of lines of dead space to insert between blasted lines
  424. Attr    DW ?    ;Attribute to be added to blasted pattern
  425. BHeight DW ?    ;Height of block to be blasted to the screen
  426. BWidth  DW ?    ;Width of block to be blasted to the screen
  427. ULY     DW ?    ;Y coordinate of upper left corner of the block
  428. ULX     DW ?    ;X coordinate of the upper left corner of the block
  429. YSize   DW ?    ;Genned max Y dimension of current visible screen
  430. XSize   DW ?    ;Genned max X dimension of current visible screen
  431. Block   DD ?    ;32-bit pointer to block pattern somewhere in memory
  432. Screen  DD ?    ;32-bit pointer to an array of pointers to screen lines
  433. ENDMRK  DB ?    ;Dummy field for stack struct size calculation
  434. ONSTACK ENDS
  435.  
  436.  
  437. CODE    SEGMENT PUBLIC
  438.         ASSUME  CS:CODE
  439.         PUBLIC  BlkBlast
  440.  
  441. BlkBlast PROC    FAR
  442.          PUSH    BP               ;Save Turbo Pascal's BP value
  443.          MOV     BP,SP            ;SP becomes new value in BP
  444.          PUSH    DS               ;Save Turbo Pascal's DS value
  445.  
  446. ;-------------------------------------------------------------------------
  447. ; If a zero is passed in TopStop, then we fill the TopStop field in the
  448. ; struct with the full size of the block, calculated by multiplying
  449. ; BWidth times BHeight.  This makes it unnecessary for the caller to
  450. ; pass the full size of the block in the TopStop parameter if topstopping
  451. ; is not required.
  452. ;-------------------------------------------------------------------------
  453.          CMP     [BP].TopStop,0   ; See if zero was passed in TopStop
  454.          JNZ     GetPtrs          ; If not, skip this operation
  455.          MOV     AX,[BP].BWidth   ; Load block width into AX
  456.          MUL     [BP].BHeight     ; Multiply by block height, to AX
  457.          MOV     [BP].TopStop,AX  ; Put the product back into TopStop
  458.  
  459. ;-------------------------------------------------------------------------
  460. ; The first important task is to get the first pointer in the ShowPtrs
  461. ; array into ES:DI.  This involved two LES operations:  The first to get
  462. ; the pointer to ShowPtrs (field Screen in the stack struct) into ES:DI,
  463. ; the second to use ES:DI to get the first ShowPtrs pointer into ES:DI.
  464. ; Remembering that ShowPtrs is an *array* of pointers, the next task is
  465. ; to index DI into the array by multiplying the top line number (ULY)
  466. ; less one (because we're one-based) by 4 using SHL and then adding that
  467. ; index to DI:
  468. ;-------------------------------------------------------------------------
  469. GetPtrs: LES     DI,[BP].Screen   ; Address of ShowPtrs array in ES:DI
  470.          MOV     CX,[BP].ULY      ; Load line address of block dest. to CX
  471.          DEC     CX               ; Subtract 1 'cause we're one-based
  472.          SHL     CX,1             ; Multiply CX by 4 by shifting it left...
  473.          SHL     CX,1             ;  ...twice.
  474.          ADD     DI,CX            ; Add the resulting index to DI.
  475.  
  476.          MOV     BX,DI            ; Copy offset of ShowPtrs into BX
  477.          MOV     DX,ES            ; Copy segment of ShowPtrs into DX
  478.          LES     DI,ES:[DI]       ; Load first line pointer into ES:DI
  479.  
  480. ;-------------------------------------------------------------------------
  481. ; The inset from the left margin of the block's destination is given in
  482. ; struct field ULX.  It's one-based, so it has to be decremented by one,
  483. ; then multiplied by two using SHL since each character atom is two bytes
  484. ; in size.  The value in the stack frame is adjusted (it's not a VAR parm,
  485. ; so that's safe) and then read from the frame at the start of each line
  486. ; blast and added to the line offset in DI.
  487. ;-------------------------------------------------------------------------
  488.          DEC     [BP].ULX         ; Subtract 1 'cause we're one-based
  489.          SHL     [BP].ULX,1       ; Multiply by 2 to cover word moves
  490.          ADD     DI,[BP].ULX      ; And add the adjustment to DI
  491.  
  492. ;-------------------------------------------------------------------------
  493. ; One additional adjustment must be made before we start:  The Deadspace
  494. ; parm puts 1 or more lines of empty space between each line of the block
  495. ; that we're blasting onto the screen.  This value is passed in the
  496. ; DEADLNS field in the struct.  It's passed as the number of lines to skip,
  497. ; but we have to multiply it by 4 so that it becomes an index into the
  498. ; ShowPtrs array, each element of which is four bytes in size.  Like ULX,
  499. ; the value is adjusted in the stack frame and added to the stored offset
  500. ; value we keep in DX each time we set up the pointer in ES:DI to blast the
  501. ; next line.
  502. ;-------------------------------------------------------------------------
  503.          SHL     [BP].DEADLNS,1   ; Shift dead space line count by 1...
  504.          SHL     [BP].DEADLNS,1   ; ...and again to multiply by 4
  505.  
  506.          LDS     SI,[BP].Block    ; Load pointer to block into DS:SI
  507.  
  508. ;-------------------------------------------------------------------------
  509. ; This is the loop that does the actual block-blasting.  Two counters are
  510. ; kept, and share CX by being separate values in CH and CL.  After
  511. ; each line blast, both pointers are adjusted and the counters swapped,
  512. ; the LOOP counter decremented and tested, and then the counters swapped
  513. ; again.
  514. ;-------------------------------------------------------------------------
  515. MovEm:   MOV     CX,[BP].BWidth            ; Load atom counter into CH
  516.          MOV     AH,BYTE PTR [BP].Attr     ; Load attribute into AH
  517. DoChar:  LODSB               ; Load char from block storage into AL
  518.          STOSW               ; Store AX into ES:DI; increment DI by 2
  519.          LOOP    DoChar      ; Go back for next char if CX > 0
  520.  
  521. ;-------------------------------------------------------------------------
  522. ; Immediately after a line is blasted from block to screen, we adjust.
  523. ; First we move the pointer in ES:DI to the next pointer in the
  524. ; Turbo Pascal ShowPtrs array.  Note that the source pointer does NOT
  525. ; need adjusting.  After blasting through one line of the source block,
  526. ; SI is left pointing at the first character of the next line of the
  527. ; source block.  Also note the addition of the deadspace adjustment to
  528. ; BX *before* BX is copied into DI, so that the adjustment will be
  529. ; retained through all the rest of the lines moved.  Finally, we subtract
  530. ; the number of characters in a line from TopStop, and see if there are
  531. ; fewer counts left in TopStop than there are characters in a block line.
  532. ; If so, we force BWidth to the number of remaining characters, and
  533. ; BHeight to one, so that we will blast only one remaining (short) line.
  534. ;-------------------------------------------------------------------------
  535.          MOV     ES,DX           ; Copy ShowPtrs segment from DX into ES
  536.          ADD     BX,4            ; Bounce BX to next pointer offset
  537.          ADD     BX,[BP].DeadLns ; Add deadspace adjustment to BX
  538.          LES     DI,ES:[BX]      ; Load next pointer into ES:DI
  539.          ADD     DI,[BP].ULX     ; Add adjustment for X offset into screen
  540.  
  541.          MOV     AX,[BP].TopStop ; Load current TopStop value into AX
  542.          SUB     AX,[BP].BWidth  ; Subtract BWidth from TopSTop value
  543.          JBE     GoHome          ; If TopStop is <= zero, we're done.
  544.          MOV     [BP].TopStop,AX ; Put TopStop value back in stack struct
  545.          CMP     AX,[BP].BWidth  ; Compare what remains in TopStop to BWidth
  546.          JAE     MovEm           ; If at least one BWidth remains, loop again
  547.          MOV     [BP].BWidth,AX  ; Otherwise, replace BWidth with remainder
  548.          JMP     MovEm           ;   and jump to last go-thru
  549.  
  550. ;-------------------------------------------------------------------------
  551. ; When the outer loop is finished, the work is done.  Restore registers
  552. ; and return to Turbo Pascal.
  553. ;-------------------------------------------------------------------------
  554.  
  555. GoHome: POP     DS                ; Restore Turbo Pascal's
  556.         MOV     SP,BP             ; Restore Turbo Pascal's stack pointer...
  557.         POP     BP                ; ...and BP
  558.         RET     ENDMRK-RETADDR-4  ; Clean up stack and return as FAR proc!
  559.                                   ;   (would be ENDMRK-RETADDR-2 for NEAR...)
  560.  
  561. BlkBlast ENDP
  562. CODE     ENDS
  563.          END
  564.  
  565.  
  566.  
  567. [LISTING FIVE]
  568.  
  569.  
  570.          TITLE  CalBlks -- External calendar pattern blocks
  571.  
  572. ; By Jeff Duntemann  --  TASM 1.0  --  Last modified 3/1/89
  573. ;
  574. ; For use with CALENDAR.PAS and BLKBLAST.ASM as described in DDJ 6/89
  575.  
  576. CODE     SEGMENT WORD
  577.          ASSUME CS:CODE
  578.  
  579.  
  580. CalFrame PROC FAR
  581.          PUBLIC CalFrame
  582.          DB   '╒═══════════════════════════╕'
  583.          DB   '│                           │'
  584.          DB   '├───┬───┬───┬───┬───┬───┬───┤'
  585.          DB   '│Sun│Mon│Tue│Wed│Thu│Fri│Sat│'
  586.          DB   '├───┼───┼───┼───┼───┼───┼───┤'
  587.          DB   '│   │   │   │   │   │   │   │'
  588.          DB   '├───┼───┼───┼───┼───┼───┼───┤'
  589.          DB   '│   │   │   │   │   │   │   │'
  590.          DB   '├───┼───┼───┼───┼───┼───┼───┤'
  591.          DB   '│   │   │   │   │   │   │   │'
  592.          DB   '├───┼───┼───┼───┼───┼───┼───┤'
  593.          DB   '│   │   │   │   │   │   │   │'
  594.          DB   '├───┼───┼───┼───┼───┼───┼───┤'
  595.          DB   '│   │   │   │   │   │   │   │'
  596.          DB   '├───┼───┼───┼───┼───┼───┼───┤'
  597.          DB   '│   │   │   │   │   │   │   │'
  598.          DB   '╘═══╧═══╧═══╧═══╧═══╧═══╧═══╛'
  599. Calframe ENDP
  600.  
  601. CalData  PROC FAR
  602.          PUBLIC CalData
  603.          DB   '   │   │   │   │   │   │   │'
  604.          DB   '  1│  2│  3│  4│  5│  6│  7│'
  605.          DB   '  8│  9│ 10│ 11│ 12│ 13│ 14│'
  606.          DB   ' 15│ 16│ 17│ 18│ 19│ 20│ 21│'
  607.          DB   ' 22│ 23│ 24│ 25│ 26│ 27│ 28│'
  608.          DB   ' 29│ 30│ 31│   │   │   │   │'
  609.          DB   '   │   │   │   │   │   │   │'
  610.          DB   '   │   │   │   │   │   │   │'
  611.  
  612. CalData  ENDP
  613.  
  614.  
  615. CODE     ENDS
  616.  
  617.          END
  618.  
  619.