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