I've managed to "fix" ReadString
to work properly with x:WordBoundary
patterns, with minimal modification of any private symbols. None, in fact. Be wary, that internally, ReadString
does BinaryReadStrin[stream, "Character8", 10000]
and then uses standard string manipulation functions on the result, whereas Read[stream, Word]
is, apparently, not written in Mathematica code. I'm really not sure, if ReadString
is intrinsically fast in this respect.
Anyways, the problem with using a pattern like WordBoundary
to read in the next word is that a string like "blahblah \n \t stuff"
has a WordBoundary
at position {1,0}
, then at position {9,8}
. One could think, that we should do repeatedly
{ReadString[stream, x:WordBoundary], Read[stream, Word]}
but that's not the case. See the linked thread for details. As Mario commented "it has some weird async stuff going on". Specifically, ReadString
reads a big chunk of 10000 bytes in, saves this to a buffer and then works through the buffer, before reading in the next chunk. Read
, on the other hand, reads stuff in from the current StreamPosition
, which, after calling ReadString
is already at the EoF
.
I suppose, these are some limitation of reading stuff in in a binary format or something. This means, that if I want to use ReadString
, then I can only use its functionality, and not that of Read
.
When I do the call ReadString[stream, x:WordBoundary]
, it runs some internal checks, then calls ProcessLink`Private`genericGetString[stream, (*pattern*), (*timeConstraint*)]
.
If I want to read in the next sequence of whitespace, that's ok, because the first position at which a WordBoundary
is found is not at the beginning of the string. If I want to read in the next word, I have a problem, because then I'm not interested in the boundary at the start of the string, but rather the one following it.
Thus, I roll my own variant of genericGetString
that does what I want, stealing most of the code from the original definition of genericGetString
.
Begin["ProcessLink`Private`"]
Global`ReadString2[st_, patt_] := genericGetString2[st, patt];
genericGetString2[st_, patt_] := Module[{str, buff = "", pos = {}},
While[
pos === {},
str = cachedReadString[st];
If[! StringQ[str],
Return[If[buff === "", str, Message[ReadString::notfound]; buff]]
];
buff = buff <> str;
pos = StringPosition[buff, patt];];
If[pos =!= {},
setStreamCache[st, StringDrop[buff, pos[[2, 2]]]];
StringTake[buff, pos[[2, 1]] - 1],
buff]
];
End[];
What I have done here, is throw the time constraint checks out the window, but most importantly, in the 3rd and 4th lines from the bottom I have changed pos[[1,2]] -> pos[[2,2]]
and pos[[1,1]] -> pos[[2,1]]
.
This now works:
readToken[str_] :=
{ReadString[str, x:(WordBoundary|EndOfString)],
ReadString2[str, x:(WordBoundary|EndOfString)]}
The result for the start of the stream is
{(* whitespace, even if of length 0 *), (* first word *)}
The result at the end of the stream is
{(* whitespace preceding last word *), (* last word *)}
{(* whitespace following last word, if none - EndOfFile *), EndOfFile}
This method fails if the value of ProcessLink`Private`binaryReadBlock
is too small. One would think, that if the procedure doesn't find, what it's looking for, it would append another chunk to the current cache, but this seems to not be the case. Maybe later I'll have a stab at fixing this oversight too.
ReadString[stream, " "|"\n"]
is a reasonable place to start tweaking. $\endgroup$ReadString
, even if I closed all streams and cleared all variables before the test. Also the value returns sometimes after other print statements, even though it claims to be a blocking function. Finally, theStreamPosition
ends up at the end of the file after one read, which is very strange. If you can overcome these though you may have a winner. $\endgroup$Alternatives[Whitespace,Whitespace]
instead of justWhitespace
and still I got the buggy behavior. I know, thatReadString
requires a file to be opened with theBinaryFormat
option, but I don't really understand how they all interact with one another. Like I said, the docs are poor on this functionality. $\endgroup$x:Whitespace
. Regarding pingbacks, the@Mario
is removed because I am the OP so I get notified even if you don't put the @ sign. If you want to use my name at the beginning of a sentence, just drop the @. $\endgroup$