There is of course a major issue and several minor issues in that code.
The major issue is using #contents.�� That's just nuts.�� All we ever *need* to holdin memory is 2 bytes, not the whole possibly extremely large file.
The factoring in my library goes like this:
BasicInputStream>>lineTerminationConvention "where the core algorithm goes"
ReadOnlyByteArray>>lineTerminationCOnvention
�� ^self readStream bindOwn: [:stream | stream lineTerminationConvention]
Filename>>lineTerminationConvention
�� ^(FileStream read: self type: #binary) bindOwn: [:stream | stream lineTerminationConvention]
and having the method on ReadOnlyByteArray made it much easier to set up test cases (which of course found a bug..)
Using Characters instead of bytes in this algorithm would be strongly inadvisable.
In astc, one of the core functions of an external text input stream is to take care of encoding, INCLUDING line terminator, so that Smalltalk code gets a Character cr *whatever* the current line ends with, making it quite impossible for the algorithm to work with Characters.�� (This is one reason why the sketch I gave above does NOT include a definition for
ReadOnlyString>>lineTerminationConvention, although it easily could; the only line termination that ever has any business turning up in a string is Character cr.�� This was very carefully engineered and makes life SO much simpler.)
Guessing the line termination convention is a matter of inspecting the EXTERNAL ENCODING of a file, just like trying to guess whether it is Latin1 or UTF8 or UTF16.�� Almost by definition you have to look at the bytes; that's what it MEANS to scrutinise the external representation.
I notice the presence of the magic number 0.8.�� Why 0.8 rather than 0.83666 or 0.7502?�� And of course this code raises an issue which I had not thought about in my code, which is "why base the decision on the proportion of the terminators rather than their recency?"�� By this I mean, suppose you write a file under Windows, some sort of log perhaps, and you get 50 lines with CR+LF terminators.�� And then you switch to appending to it from WSL, and you append another 40 lines with LF terminators.�� Shouldn't that be evidence that the convention used with the file has *changed* and it's now an LF file rather than a CRLF one?�� Perhaps updates should be computed using exponential decay, so that instead of
fooCount := fooCount��+ 1
we have
fooCount := fooCount * decayRate��+ 1.
barCount := barCount * decayRate.
ughCount := ughCount * decayRate.
It is not clear why #unknown is distinguished from #mixed.�� For my purposes, it was essential that a definite
decision was made.�� For that reason, my code *actually* uses
�� (FileStream read: self type: #binary ifAbsent: [#[] readStream]) bindOwn: [...]
to open and close the file.
And that raises the question, "what is to be done when the file does not exist".�� And in fact we need to consider four cases: file exists and is a readable file, file exists and is a readable directory, file exists but the program has no permission to read it, and file does not exist.�� Again, for my purposes, the right answers were
- file exists and is a readable file: process it
- file exists and is a readable directory: let the opening method raise a ChannelWillNotOpen exception
- file exists and is not readable: let the opening method raise a ChannelWillNotOpen exception
- file does not exist: pretend it's empty.
Calling #error: in this code seems rather pointless.�� Not that it's wrong, although it *is* an arbitrary choice not to raise an exception.�� The point is that a decision has been made here by the AI that is not grounded on anything in the prompt, and reporting #unknown or #mixed or the magic number 0.8 appear to also be decisions that are not grounded on anything in the prompt.�� Certainly not on anything in my prompt.
I want to emphasise that there is nothing special about "determine the line ending convention" and that my code is surely subject to criticism in its turn.�� And that's precisely the point.�� It's not just AIs that smuggle in decisions that are not grounded in the requirements and might make the code technically correct but unfit for purpose.�� People do it all the time.�� This is why code inspections are such a useful technique.�� There's a dance between using the requirements to debug the code and using the code to debug the requirements, where the (pragmatically)*right* thing for the AI or the human to do is to come back and ASK "What should I do with an empty file?" or "Can I assume that files will always be small compared with Smalltalk's memory?" or "Would it be OK to look at just the first line terminator?" or any other question left unanswered by the current��prompt.�� Perhaps it is up to us as programmers-using-AIs to start not by saying "write me a method <jabberwock> to <burble>" but by saying "write me some test cases for a method <jabberwock> that <burbles>."