views:

580

answers:

4

I'm processing a record-based text file: so I'm looking for a starting string which constitutes the start of a record: there is no end-of-record marker, so I use the start of the next record to delimit the last record.

So I have built a simple program to do this, but I see something which suprises me: it looks like Ruby is forgetting local variables exist - or have I found a programming error ? [although I don't think I have : if I define the variable 'message' before my loop I don't see the error].

Here's a simplified example with example input data and error message in comments:

flag=false
# message=nil # this is will prevent the issue.
while line=gets do
    if line =~/hello/ then
        if flag==true then
            puts "#{message}"
        end
        message=StringIO.new(line);
        puts message
        flag=true
    else
        message << line
    end
end

# Input File example:
# hello this is a record
# this is also part of the same record
# hello this is a new record
# this is still record 2
# hello this is record 3 etc etc
# 
# Error when running: [nb, first iteration is fine]
# <StringIO:0x2e845ac>
# hello
# test.rb:5: undefined local variable or method `message' for main:Object (NameError)
#
+2  A: 

I think this is because message is defined inside the loop. At the end of the loop iteration "message" goes out of scope. Defining "message" outside of the loop stops the variable from going out of scope at the end of each loop iteration. So I think you have the right answer.

You could output the value of message at the beginning of each loop iteration to test whether my suggestion is correct.

richj
+2  A: 

Why do you think this is a bug? The interpreter is telling you that message may be undefined when that particular piece of code executes.

ennuikiller
With respect: I don't think that's right. The interpreter is telling that message is not defined at this point. The first iteration (puts message) works.This has to be due to the scoping rules in blocks.
monojohnny
+12  A: 
JRL
+1  A: 

I'm not sure why you're surprised: on line 5 (assuming that the message = nil line isn't there), you potentially use a variable that the interpreter has never heard of before. The interpreter says "What's message? It's not a method I know, it's not a variable I know, it's not a keyword..." and then you get an error message.

Here's a simpler example to show you what I mean:

while line = gets do
  if line =~ /./ then
    puts message # How could this work?
    message = line
  end
end

Which gives:

telemachus ~ $ ruby test.rb < huh 
test.rb:3:in `<main>': undefined local variable or method `message' for main:Object (NameError)

Also, if you want to prepare the way for message, I would initialize it as message = '', so that it's a string (rather than nil). Otherwise, if your first line doesn't match hello, you end up tring to add line to nil - which will get you this error:

telemachus ~ $ ruby test.rb < huh 
test.rb:4:in `<main>': undefined method `<<' for nil:NilClass (NoMethodError)
Telemachus
To clarify why I was suprised : the statement "message=StringIO.new(line);" HAS been run once before I get the 'undefined' message: (the program DOES output the first reference to message, just not subsequent iterations). I should have probably indicated that in my output. [will ammend now]
monojohnny
+1 : I re-read this and understand it a bit better.
monojohnny