Examples:

A Generic Stream Class

Page last modified on July 05, 2014, at 07:30 AM

This is an imagined implementation of the AStream class which is part of the library used to build the first iteration of causerie's parser, and which is documented here.

Defining the class

// Most likely this definition will find its way into the standard library
<$define ABSTRACT="[ return Self subclassShouldImplement; ]"/>

// The basis for all stream classes
/* This class represents a basic binary stream that supports reading, writing,
   and random access.  The class defines several abstract methods which its
   descendants are expected to implement:
   * AStream:read:bytesInto:
   * AStream:write:bytesFrom:
   * AStream:position
   * AStream:position:
   * AStream:length

   as well as several utility routines.

   Because this class represents an abstract stream, it is not meant to be
   directly instantiated; rather, you will use one of its descendants, such
   as AFileOutputStream to write to a disk-based file or AFileInputStream to
   read from a disk-based file.
*/

AnObject subclass #AStream implements #(CanIterate) [
  public [
    // Read count bytes from the stream and store it in the destination
    long method #read: (long var #count) bytesInto: (#dest) ABSTRACT;
    // Write count bytes from the source to the stream
    long method #write: (long var #count) bytesFrom: (#source) ABSTRACT;

    // Write each value in the array to the stream
    long method #writeEach: (array var #valueList);

    // Print a string representation of the specified value to the stream
    long method #print: (#item);
    // Print a string representation of each value in the array
    long method #printEach: (array var #itemsList);

    /* 'readString' and 'writeString' are not implemented here because
       instances of 'AString' should be able to write themselves to
       a stream.
    */


    // Rewind the current position in the stream
    long method #rewindBy: (long var #count);
    // Calculate a checksum for the stream
    long method #checksum;

    // CanIterate implementation ----------------------------------------
    AnIterator method #Iterator;

    // Properties -------------------------------------------------------
    // Determine the current position within the stream
    long method #position ABSTRACT;
    // Set the current position within the stream
    long method #position: (long var #newPosition) ABSTRACT;
    // Get the current size of the stream
    long method #length ABSTRACT;
    // Determine whether or not the end of the stream has been reached
    boolean method #hasEnded;  
  ];

Some notes about the above:

  • New symbols must always be prefixed with the hash character (#) the first time they are declared. If a type is not provided for them, the compiler assumes they are of the generic type id. Both variables and method names are symbols.
  • Methods can be implemented inline or later in code. As a rule, methods should be implemented at a later point in code. Note that the preprocessor define above actually expands to an inline block that is used for those methods which should be implemented by descendant classes. If the methods are ever called directly (because a descendant did not override them), they will cause a runtime error to occur.
  • Note that position and position: refer to two different symbols; the colon makes all the difference. This is the traditional way to define accessors and mutators. The accessor method is the one that takes no parameters, while the mutator takes one or more parameters that help it to set the new value before returning the old value.

Implementing the class

AStream implementation [
  // Write each value to the stream
  long method writeEach: (array var valueList) [
    // Stores the total number of bytes written to the stream
    long var #result := 0;

    valueList forEach: { #item } [
      result += (item writeTo: Self);
    ];

    return result;
  ];

  // Print the selected item to the stream
  long method print: (#item) [
    return (item toString) printTo: Self;
  ];

  // Print each item to the stream
  long method printEach: (array var #itemsList) [
    // Stores the total number of bytes printed to the stream
    long var #result := 0;

    itemsList forEach: { #item } [
      result += ((item toString) printTo: Self);
    ];
  ];

  // Rewind the stream position pointer by the specified amount
  long method rewindBy: (long var #count) [
    // Stores the old position before any move occurs
    long var #oldPosition := Self position;
    // Stores the desired position in the stream
    long var #desiredPosition := 0;

    // Do nothing if count is zero
    (count = 0) ifTrue: [
        return oldPosition;
    ];

    // Ensure that we do not rewind beyond the beginning of the stream
    (count <= Self length) ifFalse: [
        count := Self length;
    ];

    // Calculate the new position
    desiredPosition := (Self position) - count;

    // Set the new position in the stream
    Self position: desiredPosition;

    return oldPosition;
  ];

  // Calculate a checksum for the stream
  long method checksum [
    // Stores the result
    long var #result := 0;
    // Refers to an iterator used to process the stream data
    AStreamIterator var #Data := nil;
    // Stores the position in the stream before the checksum is calculated
    long var #previousPosition := Self position;

    // Get an iterator for the stream
    Data := Self Iterator;
    // Iterate over the elements
    Data forEach: { #value } [
      // Here we call an imaginary method (for now)
      result := result crc: value;
    ];

    // Free the iterator
    Data free;

    // Return to the previous position in the stream
    Self position: previousPosition;

    return result;
  ];

  // CanIterate implementation -----------------------------------------------
  // Construct an iterator to iterate over the data in the stream
  AnIterator method Iterator [
    return AStreamIterator over: Self;
  ];

  // Properties --------------------------------------------------------------
  // Determine if the stream has ended
  boolean method hasEnded [
    // Check to see if our position (byte offset) matches our size (in bytes)
    return (Self position) >= (Self length);
  ];
];

Some notes about the above:

  • The names of any parameters expected by the method are NOT part of the method symbol; instead, they are given just as they would be for an anonymous block: in curly braces just before the block definition. The type of the parameters need not be given again, since they were already declared when the method was defined, but it is helpful to do so.

causerie

developers

recent changes

edit SideBar


All site content copyright © its various contributors.
Licensed under Creative Commons License