8.3 The implementation file

The implementation file: library-implementation.dylan.
module: timespace

// The sixty-unit class
define abstract class <sixty-unit> (<object>)
  slot total-seconds :: <integer>, init-keyword: total-seconds:;
end class <sixty-unit>;


// decode-total-seconds
define method decode-total-seconds 
    (sixty-unit :: <sixty-unit>)
 => (max-unit :: <integer>, minutes :: <integer>, seconds :: <integer>)
  decode-total-seconds(abs(time.total-seconds));
end method decode-total-seconds;


define method decode-total-seconds
    (total-seconds :: <integer>)
 => (hours :: <integer>, minutes :: <integer>, seconds :: <integer>)
  let(total-minutes, seconds) = truncate/(total-seconds, 60);
  let(hours, minutes) = truncate/(total-minutes, 60);
  values(hours, minutes, seconds);
end method decode-total-seconds;


// encode-total-seconds
define method encode-total-seconds 
    (max-unit :: <integer>, minutes :: <integer>, seconds :: <integer>) 
 => (total-seconds :: <integer>)
  ((max-unit * 60) + minutes) * 60 + seconds;
end method encode-total-seconds; 


// The say generic function
// Given an object, print a description of the object
define generic say (any-object :: <object>) => (); 


// The time classes and methods
define abstract class <time> (<sixty-unit>)
end class <time>; 


define method say (time :: <time>) => ()
  let (hours, minutes) = decode-total-seconds(time); 
  format-out
    ("%d:%s%d", hours, if (minutes < 10) "0" else "" end, minutes);
end method say; 


// A specific time of day from 00:00 (midnight) to before 24:00 (tomorrow)
define class <time-of-day> (<time>)
end class <time-of-day>; 


// A relative time between -24:00 and +24:00
define class <time-offset> (<time>)
end class <time-offset>; 


// Method for determining whether a time offset is in the past
define method past? (time :: <time-offset>) => (past? :: <boolean>)
  time.total-seconds < 0;
end method past?; 


define method say (time :: <time-offset>)
  format-out("%s ", if (past?(time)) "minus" else "plus" end);
  next-method();
end method say; 


// Methods for adding times
define method \+ 
    (offset1 :: <time-offset>, offset2 :: <time-offset>) 
 => (sum :: <time-offset>)
  let sum = offset1.total-seconds + offset2.total-seconds; 
  make(<time-offset>, total-seconds: sum); 
end method \+;


define method \+ 
    (offset :: <time-offset>, time-of-day :: <time-of-day>)
 => (sum :: <time-of-day>)
  make(<time-of-day>, 
       total-seconds: offset.total-seconds + time-of-day.total-seconds);
end method \+; 


define method \+ 
    (time-of-day :: <time-of-day>, offset :: <time-offset>)
 => (sum :: <time-of-day>)
  offset + time-of-day;
end method \+; 


define method \+ (time1 :: <time>, time2 :: <time>)
  error("Sorry, we can't add a %s to a %s.", 
        object-class(time1), object-class(time2));
end method \+; 


// Methods for comparing times
define method \< (time1 :: <time-of-day>, time2 :: <time-of-day>)
  time1.total-seconds < time2.total-seconds;
end method \<; 


define method \< (time1 :: <time-offset>, time2 :: <time-offset>)
  time1.total-seconds < time2.total-seconds;
end method \<; 


define method \= (time1 :: <time-of-day>, time2 :: <time-of-day>)
  time1.total-seconds = time2.total-seconds;
end method \=; 


define method \= (time1 :: <time-offset>, time2 :: <time-offset>)
  time1.total-seconds = time2.total-seconds;
end method \=; 


// The angle classes and methods
define abstract class <angle> (<sixty-unit>)
end class <angle>; 


define method say (angle :: <angle>) => ()
  let(degrees, minutes, seconds) = decode-total-seconds(angle);
  format-out
    ("%d degrees %d minutes %d seconds",
     degrees, minutes, seconds);
end method say; 


define class <relative-angle> (<angle>)
end class <relative-angle>;


// We need to show degrees for <relative-angle> but we do not need to
// show minutes and seconds, so we override the method on <angle>
define method say (angle :: <relative-angle>) => ()
  format-out(" %d degrees", decode-total-seconds(angle));
end method say; 


define abstract class <directed-angle> (<angle>)
  slot direction :: <string>, init-keyword: direction:;
end class <directed-angle>; 


define method say (angle :: <directed-angle>) => ()
  next-method();
  format-out(" %s", angle.direction);
end method say; 


// The latitude and longitude classes and methods
define class <latitude> (<directed-angle>)
end class <latitude>; 


define method say (latitude :: <latitude>) => ()
  next-method();
  format-out(" latitude\n");
end method say; 


define class <longitude> (<directed-angle>)
end class <longitude>; 


define method say (longitude :: <longitude>) => ()
  next-method();
  format-out(" longitude\n");
end method say; 


// The position classes and methods
define abstract class <position> (<object>)
end class <position>;


define class <absolute-position> (<position>)
  slot latitude :: <latitude>, init-keyword: latitude:;
  slot longitude :: <longitude>, init-keyword: longitude:;
end class <absolute-position>; 


define method say (position :: <absolute-position>) => ()
  say(position.latitude);
  say(position.longitude);
end method say;


define class <relative-position> (<position>)
  // Distance is in miles
  slot distance :: <single-float>, init-keyword: distance:;
  slot angle :: <angle>, init-keyword: angle:;
end class <relative-position>; 


define method say (position :: <relative-position>) => ()
  format-out("%d miles away at heading ", position.distance);
  say(position.angle);
end method say;