local variables, eval, and parsing

F

furtive.clown

I read through posts like this

http://groups.google.com/group/comp...&q=ruby+eval+local_variables#ee9c34c590b2f316

http://groups.google.com/group/comp...6fb?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?
 
F

furtive.clown

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.
 
R

Randy Kramer

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
 
F

furtive.clown

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
})
 
F

furtive.clown

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
})
 
R

Randy Kramer

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
 
F

furtive.clown

I don't know why you need the eval.

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.
 
A

ara.t.howard

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.


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/
 
P

Peña, Botp

From: ara.t.howard [mailto:[email protected]]=20
# class Config

i get a TypeError here :)

# 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
 
A

ara.t.howard

From: ara.t.howard [mailto:[email protected]]
# class Config

i get a TypeError here :)

# 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 =20
automagically creates those attributes...

# config.instance_eval IO.read(path)

kind regards -botp

if i'm following correctly, you mean this?


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

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

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

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

config =3D 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=3D42, @height=3D42.0, @configuring=3Dfalse, =20
@color=3D"pinky-beige", @honky=3Dtrue>
#<Config:0x21020 @width=3D42.0, @height=3D42.0, @configuring=3Dfalse, =20=

@color=3D"pinky-beige", @honky=3Dtrue>

??


a @ http://codeforpeople.com/
 
P

Peña, Botp

From: ara.t.howard [mailto:[email protected]]=20
# cfp:~ > cat a.rb
# require "attributes"
#=20
# class Config

arggg, i have=20

require "rubygems"
require "attributes"
class Config

and i still get a
ruby a.rb
a.rb:3: Config is not a class (TypeError)

:(

i'm on windows btw


# def self.load path
...

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

kind regards -botp
 
A

ara.t.howard

arggg, i have

require "rubygems"
require "attributes"
class Config

and i still get a

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

:(

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 :)

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

cheers.

a @ http://codeforpeople.com/
 
A

ara.t.howard

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

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 =20
have to pick another name ;-)


a @ http://codeforpeople.com/
 
F

furtive.clown

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.

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.
 
A

ara.t.howard

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/
 
F

furtive.clown

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.
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
 
S

Sean O'Halpin

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
 

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

No members online now.

Forum statistics

Threads
473,756
Messages
2,569,535
Members
45,008
Latest member
obedient dusk

Latest Threads

Top