Hi,

I'm attaching my unit TextReader, it

- implements TTextReader class (that is able to read from any TStream class using simple Readln and Eof methods) and

- initializes/finalizes some variables to treat standard input/output like a TStream classes (or, only in case of standard input, like a TTextReader class).

Works on Win32, Linux, FreeBSD (should work on any UNIX). I'm also attaching some simple demo program.

This code is part of my GNU GPLed KambiClassUtils unit on my WWW pages, I just extracted it for you to send something small.

--
Michalis

Agustin Barto wrote:
After some work I was able to translate my parser's grammar from
Coco/R to Lex/Yacc. It wasn't easy and I hate to loose the more
"functional" aspects of Coco/R, but at least my project can compile
both on Windows (with Delphi) and Linux (with fpc) (at least the
non-UI parts).

When I started to modify lexlib to work with streams instead of just
files (I have to parse stuff that could be on memory) I dicovered some
new issues:

* How can I use standard input/ouput with streams?
* Is there an easy way to do a ReadLn (or similar) with streams? The
idea is instead of rewriting the lexlib io functions, I replace the
standard io functions like Eof, ReadLn, Write and WriteLn. My initial
idea was to read the whole stream on a TStrings, but that could waste
a lot of memory.

Thanks in advance,
Agustin

_______________________________________________
fpc-pascal maillist  -  [EMAIL PROTECTED]
http://lists.freepascal.org/mailman/listinfo/fpc-pascal

uses TextReader;
begin
 while not StdInReader.Eof do
  Writeln('Next input line is "', StdInReader.Readln, '"');
end.
unit TextReader;

interface

uses SysUtils, Classes;

type
  { TTextReader reads given Stream line by line.
    Lines may be terminated in Stream with #13, #10, #13+#10 or #10+#13.
    This way I can treat any TStream quite like standard Pascal text files.

    After calling Readln or Eof you should STOP directly using underlying
    Stream (but you CAN use Stream right after creating
    TTextReader.Create(Stream) and before any Readln or Eof
    operations on this TTextReader). }
  TTextReader = class
  private
    Stream: TStream;
    ReadBuf: string;
    FOwnsStream: boolean;
  public
    { This is a comfortable constructor, equivalent to
        TTextReader.Create(TFileStream.Create(FileName, fmOpenRead), true) }
    constructor CreateFromFileStream(const FileName: string);

    { If AOwnsStream then in Destroy we will free Stream object. }
    constructor Create(AStream: TStream; AOwnsStream: boolean);
    destructor Destroy; override;

    { Reads next line from Stream. Returned string does not contain
      any end-of-line characters.
      
      Some notes about when Readln returns: when underlying stream
      contains one-letter line endings (e.g. file with UNIX
      line-endings, #10) you may notice that Readln must actually
      see 1st character of next line before it returns given line,
      e.g. sample program
         while not StdInReader.Eof do
          Writeln('Next input line is "', StdInReader.Readln, '"');
      works like that:
        user types 'foo', enter,
        user types 'bar', enter,
        program outputs 'Next input line is "foo"',
        user types 'xyz', enter,
        program outputs 'Next input line is "bar"',
        user ends input stream (Ctrl+D),
        program outputs 'Next input line is "xyz"',
      Why such "latency" in processing input ? That's because
      Readln, as implemented, must read whole line-ending before returning,
      so after #10 it must read next character (in case it would
      be #13) or EOF. }
    function Readln: string;

    function Eof: boolean;
  end;

{ ---------------------------------------------------------------------------
  Variables to read/write standard input/output using TStream classes.
  Initialized and finalized in this unit. }

var
  { Under Win32 when program is a GUI program then some
    of the variables below may be nil (although that may 
    be <> nil, even for GUI program, e.g. if user has
    run our GUI program like 
      cat something | my_program).
  
    Note that you can't simultaneously read from StdInStream
    and StdInReader (see comments at TTextReader class). }
  StdInStream, StdOutStream, StdErrStream :TStream;
  StdInReader: TTextReader;

implementation

{ TTextReader -------------------------------------------------------------- }

constructor TTextReader.CreateFromFileStream(const FileName: string);
begin
 Create(TFileStream.Create(FileName, fmOpenRead), true);
end;

constructor TTextReader.Create(AStream: TStream; AOwnsStream: boolean);
begin
 inherited Create;
 Stream := AStream;
 FOwnsStream := AOwnsStream;
end;

destructor TTextReader.Destroy;
begin
 if FOwnsStream then Stream.Free;
 inherited;
end;

function TTextReader.Readln: string;
const BUF_INC = 100;
var ReadCnt, i: integer;
    EndChar: char;
begin
 i:=1;

 { Note that ReadBuf may contain data that we
   already read from stream at some time but did not returned it to
   user of this class
   (because we realized we have read too much). }

 repeat
  if i > Length(ReadBuf) then
  begin
   SetLength(ReadBuf, Length(ReadBuf) + BUF_INC);
   ReadCnt := Stream.Read(ReadBuf[Length(ReadBuf) - BUF_INC + 1], BUF_INC);
   SetLength(ReadBuf, Length(ReadBuf) - BUF_INC + ReadCnt);
   if ReadCnt = 0 then
   begin
    Result := ReadBuf;
    ReadBuf:='';
    Exit;
   end;
  end;

  if ReadBuf[i] in [#10, #13] then
  begin
   Result := Copy(ReadBuf, 1, i-1);
   EndChar := ReadBuf[i];
   Delete(ReadBuf, 1, i);
   { From now on we can ignore value of i }
   if not Eof then
    { Eof returned false so ReadBuf <> '';
      so optionally delete second character after EndChar }
    case EndChar of
     #10: if ReadBuf[1] = #13 then Delete(ReadBuf, 1, 1);
     #13: if ReadBuf[1] = #10 then Delete(ReadBuf, 1, 1);
    end;
   Exit;
  end else
   Inc(i);
 until false;
end;

function TTextReader.Eof: boolean;
var ReadCnt: Integer;
begin
 if ReadBuf = '' then
 begin
  SetLength(ReadBuf, 1);
  ReadCnt := Stream.Read(ReadBuf[1], 1);
  SetLength(ReadBuf, ReadCnt);
 end;
 Result := ReadBuf = '';
end;

{ init / fini --------------------------------------------------------------------------}

procedure InitStdStreams;

  procedure InitStdStream(var Stream: TStream;
    {$ifdef WIN32}nStdHandle: DWord{$endif}
    {$ifdef UNIX}Handle: THandle{$endif});
  {$ifdef WIN32}var Handle: THandle;{$endif}
  begin
   {$ifdef UNIX}
   Stream := THandleStream.Create(Handle);
   {$else}
   Handle := GetStdHandle(nStdHandle);
   if Handle <> INVALID_HANDLE_VALUE then
    Stream := THandleStream.Create(Handle) else
    Stream := nil;
   {$endif}
  end;

begin
 InitStdStream(StdInStream,  {$ifdef WIN32} STD_INPUT_HANDLE  {$else} StdInputHandle  {$endif});
 InitStdStream(StdOutStream, {$ifdef WIN32} STD_OUTPUT_HANDLE {$else} StdOutputHandle {$endif});
 InitStdStream(StdErrStream, {$ifdef WIN32} STD_ERROR_HANDLE  {$else} StdErrorHandle  {$endif});
 if StdInStream <> nil then
  StdInReader := TTextReader.Create(StdInStream, false) else
  StdInReader := nil;
end;

procedure FiniStdStreams;
begin
 FreeAndNil(StdInStream);
 FreeAndNil(StdOutStream);
 FreeAndNil(StdErrStream);
 FreeAndNil(StdInReader);
end;

initialization
 InitStdStreams;
finalization
 FiniStdStreams;
end.
_______________________________________________
fpc-pascal maillist  -  [EMAIL PROTECTED]
http://lists.freepascal.org/mailman/listinfo/fpc-pascal

Reply via email to