views:

178

answers:

5

short:

Is there a way in Ruby to DRY-ify this:

def entry_point_one
  begin
    do_something
  rescue MySyntaxErrorOne, MySyntaxErrorTwo, MySyntaxErrorEtc => syn_err
    raise syn_err.exception(syn_err.message)
  end
end

def entry_point_two
  begin
    do_something_else
  rescue MySyntaxErrorOne, MySyntaxErrorTwo, MySyntaxErrorEtc => syn_err
    raise syn_err.exception(syn_err.message)
  end
end

longer:

I'm building an interpreter. This interpreter can be called using different entry points. If I feed this interpreter a 'dirty' string, I expect it to raise an error. However, it would be nice if I don't get spammed by the by the entire back trace of every method called directly or indirectly by do_something, especially since the interpreter makes use of recursion.

As you can see in the above snippet, I already know a way to re raise an error and thereby removing the back trace. What I would like do is remove the duplication in the above example. The closest I have come thus far is this:

def entry_point_one
  re_raise_known_exceptions {do_something}
end

def entry_point_two
  re_raise_known_exceptions {do_something_else}
end

def re_raise_known_exceptions
  yield
rescue MySyntaxErrorOne, MySyntaxErrorTwo, MySyntaxErrorEtc => syn_err
    raise syn_err.exception(syn_err.message)
end

But that makes the method re-raise-known-exceptions show up in the back trace.

edit: I guess what I want would be something like a C pre-processing macro

+2  A: 

while thinking about it a bit more, I came up with this:

interpreter_block {do_something}

def interpreter_block
  yield
rescue ExceptionOne, ExceptionTwo, ExceptionEtc => exc
  raise exc.exception(exc.message)
end

Although it's still not quiet what I would like to have, at least now the extra entry in the back trace has become somewhat better looking.

Patrick Huizinga
A: 

If you have all of the information you need in the exceptions, and you do not need the backtrace at all, you can just define your own error and raise that, instead of reraising the existing exception. This will give it a fresh backtrace. (Of course, presumably your sample code is incomplete and there is other processing happening in the rescue block -- otherwise your best bet is to just let the error bubble up naturally.)

class MyError < StandardError; end

def interpreter_block
  yield
rescue ExceptionOne, ExceptionTwo, ExceptionEtc => exc
  raise MyError
end
Ian Terrell
I still want the back trace, but I don't want the 31.4 nested methodsfrom the interpreter to show up. The exceptions that I want to catch are exceptions that get thrown because of errors in the fed string.The back trace inside the interpreter is therefor not as important as the exception itself
Patrick Huizinga
+3  A: 

You can just use the splat on an array.

Straight from IRB:

COMMON_ERRORS = [ArgumentError, RuntimeError] # add your own 

def f
  yield
rescue *COMMON_ERRORS => err
  puts "Got an error of type #{err.class}"
end


f{ raise ArgumentError.new }
Got an error of type ArgumentError

f{ raise 'abc' }
Got an error of type RuntimeError
Orion Edwards
Although it's not what I want, thanks for showing that plat thing.
Patrick Huizinga
Oh, I see what you're getting at now, sorry
Orion Edwards
A: 

It might be slightly evil, but I think you can simply remove the line from the backtrace ;-)

COMMON_ERRORS = [ArgumentError, RuntimeError]

def interpreter_block
  yield
rescue *COMMON_ERRORS => err
  err.backtrace.delete_if{ |line| line=~/interpreter_block/ }
  raise err
end

I'm not sure it's such a good idea though. You'll have a hell of a lot of fun debugging your interpreter afterward ;-)

Side note: Treetop may be of interest to you.

webmat
It won't work, because you raise inside interpreter_block *after* trying to delete that part of the back trace.
Patrick Huizinga
By the way, I don't care about anything evil, because the exceptions are supposed to notify about errors in the to-be-interpreted string, not about the interpreter itself.
Patrick Huizinga
A: 

This is a touch hackish, but as far as cleaning up the backtrace goes, something like this works nicely:

class Interpreter

  def method1
    error_catcher{ puts 1 / 0 }
  end

  def error_catcher
    yield
  rescue => err
    err.set_backtrace(err.backtrace - err.backtrace[1..2])
    raise err
  end

end

The main trick is this line err.set_backtrace(err.backtrace - err.backtrace[1..2]). Without it, we get the following (from IRB):

ZeroDivisionError: divided by 0
  from (irb):43:in `/'
  from (irb):43:in `block in method1'
  from (irb):47:in `error_catcher'
  from (irb):43:in `method1'
  from (irb):54
  from /Users/peterwagenet/.ruby_versions/ruby-1.9.1-p129/bin/irb:12:in `<main>'

What we don't want in there are the second and third lines. So we remove them, ending up with:

ZeroDivisionError: divided by 0
  from (irb):73:in `/'
  from (irb):73:in `method1'
  from (irb):84
  from /Users/peterwagenet/.ruby_versions/ruby-1.9.1-p129/bin/irb:12:in `<main>'
Peter Wagenet