How to run a steam locomotive (SL) through a pipe

Hello. Do you know the command sl ?

This will make a steam locomotive run in the terminal.

sl

Let’s try passing this SL through a pipe to your command line tool.

The following code works in both Ruby and Crystal.

portal.rb / portal.cr

ARGF.each_line do |line|
  puts line
end

Ruby:

ruby_sl

Crystal:

crystal_sl

As you can see, the output of the steam locomotive collapses in Crystal.
Where do you think this difference comes from?

3 Likes

This is a quite interesting issue!

In my terminal, piping sl through the Crystal portal behaves a bit differently: It only prints the first frame, then hangs until sl terminates. No further frames are printed.
Dunno what’s going wrong there. sl itself works just fine.

There seems to be some issue with the line buffering I suppose.

The Crystal implementation can be changed to IO.copy(ARGF, STDOUT) which operates without line buffering and thus is much more efficient. And more importantly, it behaves correctly.

I still have no idea what’s the difference between the Ruby and Crystal implementations though. And why my terminal only shows a single frame in the line-based implementation.

5 Likes

The following code works better.
There may be a difference in chomp behavior between Crystal and Ruby.

ARGF.each_line(chomp: false) do |line|
  print line
end

On Ubuntu, my terminal only shows a single frame.
There seems to be a difference depending on the OS.

There is a difference in puts behaviour. If it needs to print a newline character, Ruby uses a single action with writev while in Crystal it’s two separate calls to write. Maybe that makes a difference.

2 Likes

On Arch use wezterm is same.

1 Like

It wasn’t as interesting as I thought it would be.

In Ruby, the default behavior of each_line is not to execute chomp (it does not remove the newline).

each_line(rs = $/, chomp: false) {|line| ... } -> self

In Crystal, it does execute chomp (it does remove the newline).

def each_line(chomp = true, &block : String -> ) : Nil

That’s the difference.
In other words, Ruby outputs one extra line break.

However, I learned that the method of terminal buffering seems to differ depending on the environment.

2 Likes

Hmmm. I think this specific issue has been resolved, but there are still other behaviors hidden that are hard to explain…