Local variables, eval, and parsing

I read through posts like this

http://groups.google.com/group/comp.lang.ruby/browse_thread/thread/18c16150d26df98e/ee9c34c590b2f316?lnk=st&q=ruby+eval+local_variables#ee9c34c590b2f316

http://groups.google.com/group/comp.lang.ruby/browse_thread/thread/fd399f1d82be73a8/4c0df13f35d7e6fb?lnk=st&q=ruby+eval+local#4c0df13f35d7e6fb

but was still not satisfied with the solutions given. For example

   val = nil
   eval "val = 12"
   p val

The mention of 'val' twice is a maintenance problem, as you need to
manually update the local variables in code whenever the variables
inside the string change.

   val = nil # forgot to update -- manual labor is hard work
   eval "first_val = 12 ; second_val = 44" # string has changed
   p first_val # oops!

One solution is to change those locals into attributes of the
singleton:

def locals_to_accessors(str)
   previous_locals = local_variables
   eval str

   (local_variables - previous_locals).each { |name|
      value = eval name
      eval %Q{
         class << self
            attr_accessor :#{name}
         end
      }
      self.send("#{name}=", value)
   }
end

locals_to_accessors %q{
   val = 12
   foo = "bar"
}

# no errors!
p val
p foo

This seems like a gymnastic workaround. All I want to do insert some
local variables from a config file. Is there really no way to do that?

val = 44
p val # => 44
p val() # => 12

So this "solution" is not too great. I made the writer private but
that doesn't help since the local declaration apparently takes
precedence.

As explained in the links I gave, the problem is due to the
determination of local variables at compile time. Really all that is
needed is a way to insert code before parsing, like perl's BEGIN block.

Ruby has a BEGIN {} (and END) block as well--have you tried that? (pickaxe2
pg. 318)

Randy Kramer

···

On Thursday 08 November 2007 11:25 am, furtive.clown@gmail.com wrote:

As explained in the links I gave, the problem is due to the
determination of local variables at compile time. Really all that is
needed is a way to insert code before parsing, like perl's BEGIN block.

BEGIN {
   eval "foo = 99"
}
puts foo

=> test.rb:6: undefined local variable or method `foo' for main:Object
(NameError)

I realized after posting this that BEGIN wasn't the right analogy as
it still executes too late, after parsing is already done. Literal
code needs to be inserted into the buffer before parsing, something
like a C-preprocessor #include.

The original motivation was to have a config file for my Rakefile. An
ugly workaround is to move the content into Rakefile.main and make a
one-liner Rakefile:

eval(File.open("Rakefile.config") { |f|
        f.read
     } + "\n" +
     File.open("Rakefile.main") { |f|
        f.read
     })

···

On Nov 8, 1:10 pm, Randy Kramer <rhkra...@gmail.com> wrote:

On Thursday 08 November 2007 11:25 am, furtive.cl...@gmail.com wrote:

> As explained in the links I gave, the problem is due to the
> determination of local variables at compile time. Really all that is
> needed is a way to insert code before parsing, like perl's BEGIN block.

Ruby has a BEGIN {} (and END) block as well--have you tried that? (pickaxe2
pg. 318)

BEGIN {
   eval "foo = 99"
}
puts foo

=> test.rb:6: undefined local variable or method `foo' for main:Object
(NameError)

I realized after posting this that BEGIN wasn't the right analogy as
it still executes too late, after parsing is already done. Literal
code needs to be inserted into the buffer before parsing, something
like a C-preprocessor #include.

The original motivation was to have a config file for my Rakefile. An
ugly workaround is to move the content into Rakefile.main and make a
one-liner Rakefile:

eval(File.open("Rakefile.config") { |f|
        f.read
     } + "\n" +
     File.open("Rakefile.main") { |f|
        f.read
     })

···

On Nov 8, 1:10 pm, Randy Kramer <rhkra...@gmail.com> wrote:

On Thursday 08 November 2007 11:25 am, furtive.cl...@gmail.com wrote:

> As explained in the links I gave, the problem is due to the
> determination of local variables at compile time. Really all that is
> needed is a way to insert code before parsing, like perl's BEGIN block.

Ruby has a BEGIN {} (and END) block as well--have you tried that? (pickaxe2
pg. 318)

>
> > As explained in the links I gave, the problem is due to the
> > determination of local variables at compile time. Really all that is
> > needed is a way to insert code before parsing, like perl's BEGIN block.
>
> Ruby has a BEGIN {} (and END) block as well--have you tried that?

(pickaxe2

> pg. 318)
>

BEGIN {
   eval "foo = 99"
}
puts foo

=> test.rb:6: undefined local variable or method `foo' for main:Object
(NameError)

Hmm, I probably can't help you--pointing out the BEGIN block might have been
the limits of my knowledge, but...

Are you sure the undefined local variable is a parsing problem? Look at this:

The following gives me the (same) undefined local variable error that you get:

./test.rb:3: undefined local variable or method `foo' for main:Object
(NameError)

#! /usr/bin/env ruby
BEGIN { foo = 99 }
puts foo

On the other hand, making it an instance variable (a little more towards the
global range of things) makes it work just fine.

#! /usr/bin/env ruby
BEGIN { @foo = 99 }
puts @foo

I don't know why you need the eval.

Good luck!
Randy Kramer

···

On Thursday 08 November 2007 01:45 pm, furtive.clown@gmail.com wrote:

On Nov 8, 1:10 pm, Randy Kramer <rhkra...@gmail.com> wrote:
> On Thursday 08 November 2007 11:25 am, furtive.cl...@gmail.com wrote:

I realized after posting this that BEGIN wasn't the right analogy as
it still executes too late, after parsing is already done. Literal
code needs to be inserted into the buffer before parsing, something
like a C-preprocessor #include.

The original motivation was to have a config file for my Rakefile. An
ugly workaround is to move the content into Rakefile.main and make a
one-liner Rakefile:

eval(File.open("Rakefile.config") { |f|
        f.read
     } + "\n" +
     File.open("Rakefile.main") { |f|
        f.read
     })

Remember the motivation was to have a separate config file for these
variables. Unless ruby has something akin to the C-preprocessor
#include then we are stuck with reading a file, which means we are
stuck with a string, which means we are stuck with eval, which means
we are stuck with ugly hacks in order to obtain local variables.

···

On Nov 8, 4:39 pm, Randy Kramer <rhkra...@gmail.com> wrote:

I don't know why you need the eval.

cfp:~ > cat a.rb
require "attributes" ### gem install attributes

class Config
   attribute "width"
   attribute "height"
   attribute "color"

   def self.load path
     config = new
     config.instance_eval IO.read(path)
     config
   end

   def configure &block
     instance_eval &block
   end
end

config = Config.load "b.rb"

p config

config.configure{ width 42.0 }

p config

cfp:~ > cat b.rb
configure {
   width 42

   height 42.0

   color "pinky-beige"
}

cfp:~ > ruby a.rb
#<Config:0x1ea3c @color="pinky-beige", @height=42.0, @width=42>
#<Config:0x1ea3c @color="pinky-beige", @height=42.0, @width=42.0>

it's true that they are not local vars but i think the effect is useful enough.

a @ http://codeforpeople.com/

···

On Nov 8, 2007, at 3:04 PM, furtive.clown@gmail.com wrote:

Remember the motivation was to have a separate config file for these
variables. Unless ruby has something akin to the C-preprocessor
#include then we are stuck with reading a file, which means we are
stuck with a string, which means we are stuck with eval, which means
we are stuck with ugly hacks in order to obtain local variables.

--
it is not enough to be compassionate. you must act.
h.h. the 14th dalai lama

# class Config

i get a TypeError here :slight_smile:

# attribute "width"
# attribute "height"
# attribute "color"

is it possible without declaring those above?
i mean, something like some sort of attrib_eval(like below) but automagically creates those attributes...

# config.instance_eval IO.read(path)

kind regards -botp

···

From: ara.t.howard [mailto:ara.t.howard@gmail.com]

Thanks for the response. Actually I previously abandoned this kind of
setup because it was inconsistent with the rest of my rakefile. I
don't want a clear distinction between config variables and regular
variables --- in fact, the blurrier the better. If I decide to move
one variable into the config section, there should be no associated
code changes.

One solution is to put all my variables in that config class,
whereupon I would call it class Stuff. But then the rakefile is
messier and harder to manage: "s.foo ; s.bar ;" instead of "foo ;
bar ;". I can avoid these "s." prefixes by putting everything inside
an instance_eval, but then I am back to exactly the same problem I had
in my second post in this thread. Namely, "foo = 99" creates a local
variable 'foo' instead of calling Stuff#foo=, whereupon I have two
'foo's and all hell breaks loose.

···

On Nov 8, 9:30 pm, "ara.t.howard" <ara.t.how...@gmail.com> wrote:

cfp:~ > cat a.rb
require "attributes" ### gem install attributes

class Config
   attribute "width"
   attribute "height"
   attribute "color"
   # ...
end

cfp:~ > cat b.rb
configure {
   width 42

   height 42.0

   color "pinky-beige"
}

it's true that they are not local vars but i think the effect is
useful enough.

if i'm following correctly, you mean this?

cfp:~ > cat a.rb
require "attributes"

class Config
   def self.load path
     config = new
     config.configure IO.read(path)
     config
   end

   def configure *a, &b
     @configuring = true
     instance_eval *a, &b
   ensure
     @configuring = false
   end

   def method_missing m, *a, &b
     super unless @configuring
     attribute m
     send m, *a, &b
   end
end

config = Config.load "b.rb"

p config

config.configure{ width 42.0 }

p config

cfp:~ > cat b.rb
configure {
   width 42

   height 42.0

   color "pinky-beige"

   honky true
}

cfp:~ > ruby a.rb
#<Config:0x21020 @width=42, @height=42.0, @configuring=false, @color="pinky-beige", @honky=true>
#<Config:0x21020 @width=42.0, @height=42.0, @configuring=false, @color="pinky-beige", @honky=true>

??

a @ http://codeforpeople.com/

···

On Nov 8, 2007, at 8:14 PM, Peña, Botp wrote:

From: ara.t.howard [mailto:ara.t.howard@gmail.com]
# class Config

i get a TypeError here :slight_smile:

# attribute "width"
# attribute "height"
# attribute "color"

is it possible without declaring those above?
i mean, something like some sort of attrib_eval(like below) but automagically creates those attributes...

# config.instance_eval IO.read(path)

kind regards -botp

--
we can deny everything, except that we have the possibility of being better. simply reflect on that.
h.h. the 14th dalai lama

Thanks for the response. Actually I previously abandoned this kind of
setup because it was inconsistent with the rest of my rakefile. I
don't want a clear distinction between config variables and regular
variables --- in fact, the blurrier the better. If I decide to move
one variable into the config section, there should be no associated
code changes.

sounds very confusing to maintain to me, but it's your party

One solution is to put all my variables in that config class,
whereupon I would call it class Stuff. But then the rakefile is
messier and harder to manage: "s.foo ; s.bar ;" instead of "foo ;
bar ;". I can avoid these "s." prefixes by putting everything inside
an instance_eval, but then I am back to exactly the same problem I had
in my second post in this thread. Namely, "foo = 99" creates a local
variable 'foo' instead of calling Stuff#foo=, whereupon I have two
'foo's and all hell breaks loose.

not so, attributes allows a default block and getter as setter. so you don't need to say

   s.foo = 99

just

   foo 99

if you move your code to using attributes it's quite easy to to allow it to be externally configured. your other alternative is using @instance_vars. you cannot set a local variable via eval and ruby provides no #include syntax so simply cannot do what you are trying to do - it's swimming upstream.

regards.

a @ http://codeforpeople.com/

···

On Nov 9, 2007, at 5:55 AM, furtive.clown@gmail.com wrote:
--
share your knowledge. it's a way to achieve immortality.
h.h. the 14th dalai lama

# cfp:~ > cat a.rb
# require "attributes"

···

From: ara.t.howard [mailto:ara.t.howard@gmail.com]
#
# class Config

arggg, i have

require "rubygems"
require "attributes"
class Config

and i still get a

ruby a.rb

a.rb:3: Config is not a class (TypeError)

:frowning:

i'm on windows btw

# def self.load path
...

ah, yes. very clever. that could make for another config gem
magical auto attributes indeed :slight_smile:

kind regards -botp

> Thanks for the response. Actually I previously abandoned this kind of
> setup because it was inconsistent with the rest of my rakefile. I
> don't want a clear distinction between config variables and regular
> variables --- in fact, the blurrier the better. If I decide to move
> one variable into the config section, there should be no associated
> code changes.

sounds very confusing to maintain to me, but it's your party

Well it's the opposite. Having to adjust the syntax of each mention
of a variable just because you moved the variable into a config file
is confusing to maintain.

> One solution is to put all my variables in that config class,
> whereupon I would call it class Stuff. But then the rakefile is
> messier and harder to manage: "s.foo ; s.bar ;" instead of "foo ;
> bar ;". I can avoid these "s." prefixes by putting everything inside
> an instance_eval, but then I am back to exactly the same problem I had
> in my second post in this thread. Namely, "foo = 99" creates a local
> variable 'foo' instead of calling Stuff#foo=, whereupon I have two
> 'foo's and all hell breaks loose.

not so, attributes allows a default block and getter as setter. so
you don't need to say

   s.foo = 99

just

   foo 99

But like I said above, it's a maintenance problem. Setting some
variables with "foo 99" and others with "bar = 88" is confusing. When
I decide to place bar in a config file, I have to change all mentions
of "bar = blah" to "bar blah". Not using local variables at all would
achieve consistency but would introduce a different flavor of
confusion.

you cannot set a local variable via eval

Not so:

   val = nil
   eval "val = 12"
   p val # => 12

···

On Nov 9, 10:30 am, "ara.t.howard" <ara.t.how...@gmail.com> wrote:

On Nov 9, 2007, at 5:55 AM, furtive.cl...@gmail.com wrote:

arggg, i have

require "rubygems"
require "attributes"
class Config

and i still get a

ruby a.rb

a.rb:3: Config is not a class (TypeError)

:frowning:

i'm on windows btw

looks to be rubygems

cfp:~ > ruby -e' require "rubygems"; class Config; end; p 42 '
-e:1: Config is not a class (TypeError)

cfp:~ > ruby -e' require "attributes"; class Config; end; p 42 '
42

wanna file a bug report? otherwise i can.

# def self.load path
...

ah, yes. very clever. that could make for another config gem
magical auto attributes indeed :slight_smile:

hmmm. i'd probably give it a little more thought - but that's not a bad idea. what else would such a beast do?

cheers.

a @ http://codeforpeople.com/

···

On Nov 8, 2007, at 9:46 PM, Peña, Botp wrote:
--
it is not enough to be compassionate. you must act.
h.h. the 14th dalai lama

just realized i'd seen this before:

cfp:~ > ruby -e' require "rbconfig"; class Config; end; p 42 '
-e:1: Config is not a class (TypeError)

so it's not gems, but ruby dropping Config up there. guess we'll have to pick another name :wink:

a @ http://codeforpeople.com/

···

On Nov 8, 2007, at 9:46 PM, Peña, Botp wrote:

a.rb:3: Config is not a class (TypeError)

--
we can deny everything, except that we have the possibility of being better. simply reflect on that.
h.h. the 14th dalai lama

Maybe something like this is what you're after?

config = "foo = 42"
source = "def config_vars; #{config}; binding; end; config_vars"
kv = eval("local_variables.inject({}) {|h,v| h[v] = eval(v); h}", eval(source))
p kv
# >> {"foo"=>42}

You might want to use a random string for the method name to avoid
name collisions or use an anonymous module but the basics are here.

Regards,
Sean