IRB module with non-blocking asynchronous gets

S

Shane Liesegang

Is there a way to use the IRB module so that it evaluates code on-demand
only instead of running in a constant loop?

I don't need to use readline and have IRB wait for input -- I can
trigger its need to evaluate from elsewhere in the program. I was
hoping, though, that I could have the gets function from my
IRB::StdioInputMethod subclass return a properly formatted string, let
IRB do its thing, and then give me back control until it was needed
again.

It seems, though, that the eval_input function just keeps executing gets
and never returns control. I understand that's the intended behavior of
eval_input, but I'm a little surprised there's not a more asynchronous
way of requesting evaluation.

Am I just approaching the problem from the wrong angle? I keep feeling
like Ruby has to provide a way to do what I'm after, but I haven't been
able to track it down thus far.

Any help would be appreciated.
 
J

James Edward Gray II

Is there a way to use the IRB module so that it evaluates code on-
demand
only instead of running in a constant loop?

I'm working on my glue code speech for Lone Star Rubyconf today so my
first thought was:

#!/usr/bin/env ruby -wKU

class IRbOnDemand
def initialize
@irb = IO.popen("irb -f --simple-prompt --noreadline", "r+")
end

def run(ruby)
@irb.puts ruby
@irb.flush

@irb.each do |line|
return eval($1) if line =~ /\A=>\s*(.+)/
end
end
end

irb = IRbOnDemand.new
result = irb.run %Q{a = %w[words] * 3}
puts "Result: #{result.inspect}"

puts "Doing other things..."
sleep 3

result = irb.run %Q{a}
puts "Result: #{result.inspect}"

__END__

Is that what you are after? If it is, do we really need IRb at all?
How's this:

#!/usr/bin/env ruby -wKU

class IRbOnDemand
def initialize
@binding = binding
end

def run(ruby)
eval ruby, @binding
end
end

irb = IRbOnDemand.new
result = irb.run %Q{a = %w[words] * 3}
puts "Result: #{result.inspect}"

puts "Doing other things..."
sleep 3

result = irb.run %Q{a}
puts "Result: #{result.inspect}"

__END__

James Edward Gray II
 
S

Shane Liesegang

Hi James. Thanks for the recommendations.

James said:
Is that what you are after? If it is, do we really need IRb at all?
How's this:

I could just use eval to run the strings; that's true. I was hoping to
leverage IRB's prompt mechanism and the ability to define functions on
the fly. As below:

-----
irb(main):001:0> def test
irb(main):002:1> puts "testing..."
irb(main):003:1> end
=> nil
irb(main):004:0> test
testing...
=> nil
irb(main):005:0>
-----

I'm finding that it's pretty tricky, though. I managed to make it
non-blocking for a single line by overriding eval_input and having it
break after evaluating a line instead of waiting for input. That seems
to work fine, but when doing any kind of multi-line input, it still goes
into a loop that it doesn't want to return from.

It seems like IRB, to its core, is based on constantly waiting for
input, and trying to make it work in an on-demand fashion is like
running uphill. I'm still rooting through its source, trying to find the
spots where I can poke at it to make it work that way, but it's slow
going with a large mass of interrelated code that's not terribly easy
for humans to parse. :)
 
S

Shane Liesegang

Giles said:
is that what you need, basically?

Close, but it doesn't let me enter arbitrary lines of input and know
whether they need to be joined to execution or can be executed
individually, the way IRB does.

I've got the majority of my desired behavior now, by applying some
patches to RubyLex: http://pastie.caboo.se/91797

When the user presses enter, I call get_statement on the lexer, which
either returns the line and line number if it's ready for execution or
nil if it's not.

The only thing this doesn't currently handle is string continuations --
once again, IRB happily enters an infinite loop when it detects an
incomplete string in a line. I would like to hit this border case as
well, but it's looking like it's going to require some pretty deep digs
into the lexing code. It doesn't terribly excite me, and I don't
personally use string continuation very much, so I'm tempted to just
call it a halfway-victory.

Unless there are some particularly knowledgeable IRB hackers waiting in
the wings to offer advice...
 
S

Shane Liesegang

For anyone still reading this thread (or who stumbles across it in the
future), I have been able to solve the string continuation problem -- it
may be a bit inefficient, but I run my own processing on the line before
trying to pass it to the lexer. If I determine that it's the start of a
string continuation, I set the flags appropriately.

No code in pastie this time, but the algorithm for finding unfinished
strings is pretty straightforward, provided you cover all the cases
provided in Ruby (single quotes, double quotes, %q, %w, %x, %Q, %W, %X,
and heredocs).

I leave it as an exercise to the reader. :)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,772
Messages
2,569,593
Members
45,108
Latest member
AlbertEste
Top